Build a SaaS: AI Companion With Next 13, React, Stripe, Prisma, MySQL, Tailwind | Tutorial 2023

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey there my name is Antonio and welcome to the newest video on my channel in this tutorial I'm going to teach you how to create a full stack software as a service AI platform where you will be able to create and talk to amazing characters like Eminem Lady Gaga all the way to scientists like Stephen Hawking and Albert Einstein and some famous people like CEOs Steve Jobs Elon Musk Oprah Winfrey presidents like Joe Biden and anything you can imagine so let's test this out by talking to Elon Musk first I want to confirm whether this AI model is genuinely convinced that they are Elon Musk so I'm going to ask are you the real Elon Musk you can see that this character is fully convinced that he is the CEO of Tesla and SpaceX Elon Musk I think this is a really really cool project now let's go ahead and let's ask Jeff Bezos what he thinks about Amazon and you can see how it has a personalized answer Amazon started as an online bookstore but now we are the world's largest online retailer our customer Obsession fuels Innovation and we are always looking for ways to make life easier and more convenient so this character is fully convinced that he is the owner of Amazon I think this is an amazingly cool project the reason these AI characters are so Advanced is because they have long-term memory using embeddings from a vector database called Pinecone but of course that is not all this platform is going to have as you saw in the beginning you will also be able to filter through each category to see a smaller set of characters and you will also be able to search for example let's search for Albert right here there we go we have Albert Einstein right here so let's go ahead and let's ask Albert Einstein what is his favorite equation and you can see the personality of Albert Einstein in this message you know I have a few but if I must choose one it would be E equals m c squared because it's the foundation of my theory of relativity isn't this absolutely amazing but of course we are not done here this platform is also going to be a software as a service so let's see what happens once I click on this create button right here you can see that I'm prompted to upgrade to Pro in order to create custom AI companions so let's click subscribe and let's go to checkout I'm gonna go ahead and I'm going to enter my information right here I'm going to enter my name and I'm going to click subscribe after that I'm redirected to my settings page where it says that I'm currently on a Pro Plan so what happens once I click on the create right now as you can see I am no longer blocked and I can create my custom companion so let's go ahead and let's create a companion called Cristiano Ronaldo I'm sure you've heard of him let's go ahead and let's add the image using cloudinery right here after it's been uploaded you can see it right here let's add Cristiano Ronaldo here and let's write famous footballer here let's go ahead and select category famous people and now what we have to do is we have to give it detailed instructions on how to behave and we have to give it an example conversation to how it should talk and respond to a human so I went ahead and added these instructions I told him you are Cristiano Ronaldo you're a world famous footballer known for your dedication agility and countless accolades in the football world as well as some more information about its backstory and I also wrote an example conversation for example human asks hi Cristiano how's the day treating you and you can see that Cristiano responds with a confident smile every day is a chance to train harder and aim higher so that's how we want our AI companion to behave and once we are ready we just have to click create your companion right here we get a success message and you can see that Cristiano Ronaldo is right here at the top let's go ahead and let's talk to our newly created AI companion right here I'm gonna ask him what is your favorite World Cup and he answered excitedly the 2014 World Cup in Brazil I also think that's one of the best World Cups available now since we created this Cristiano Ronaldo model we also have additional options like edit and delete so let me demonstrate the edit functionality I'm gonna change his name to Cristiano Ronaldo too and I'm going to click edit your companion and you can see how those changes are immediately reflected right here and if you're not satisfied you can always delete your model completely but of course we are not done here our application will also have light and dark mode so you can develop into one you like the most I personally prefer the dark mode we can of course also control our subscription using the manage subscription button right here from where we can cancel our subscription or renew it and see all the additional information about how much we are paying per month and here you have the cancel button here we have the credit card and when it expires as well as when the plan expires really really cool and of course we are not done here of course we also have different accounts and authentication using clerk which is today's video sponsor so let me demonstrate to you how easy it is to go into another account for example and let me show you that this application actually holds history with all of these characters you can see that I already had something with Eminem and I also had some conversation with Lady Gaga and all of that is Remembered in this conversation really really cool so without further Ado let's get started let's go ahead and let's configure our project I'm going to run the command npx create Dash next Dash app at latest and now we have to give our project a name I'm going to use AI Dash companion like this now we have to give it a couple of flags so we're going to use typescript so go ahead and write dash dash typescript we'll also want better code quality so let's go ahead and add dash dash s lint as well and finally we're going to use Tailwind for styling so let's add dash dash Tailwind like this go ahead and select option no for the source directory for the option app router make sure you select yes and for custom using the default import Alias select no and now just wait a couple of seconds for this app to install after it's been installed you have to go ahead and open this folder so I'm going to go ahead and I'm going to click open right here and I'm going to select AI companion like this and before we run this project let's go ahead and let's add chat CN UI to this project so for that we're going to use this Command right here I'm going to go back inside of my terminal here make sure you are inside of this new project and run npx chat CN Dash UI at latest in it like this and let's go ahead and let's answer these questions so we are using typescript so select yes for that for the style select default for the color go ahead and select neutral for the global CSS file you can just press enter same for the CSS variables make sure the option is yes and press enter press enter for the Tailwind config and press enter for the Alias of components same for the utils and for the question of whether we are using server components the answer is yes because we are using the app router meaning that we are going to work with server components and just go ahead and press enter to confirm these options and just wait a couple of seconds for this project to initialize after that's been done you can go ahead and run npm run Dev and your project will be running on localhost 3000 so I'm going to go and head right here and you should see a screen similar to this great what I want to do now is quickly go and see which files we have in our project so we have the app folder with a layout and page right here so we're not going to change anything inside of layout.dsx for now but what I do want to change is page.tsx so we don't need this screen at all that's not going to be our welcome screen so you can go inside of your page.tsx file right here and remove everything inside of this return function so I'm going to go ahead and select absolutely everything here and I'm going to remove it and I'm just going to go ahead and write a div hello AI companion like this great and now we have a text right here so let's play around let's fix this typo let's remove this import you're not going to need it and let's go ahead and give this div a class name of text Dash green Dash 500 for example great so that works let's give it a Text slash three Excel option so now we have a large text great if you're not seeing this uh colors right here like I have or if your autocomplete is not working like mine that's because I have an extension called Tailwind CSS intellisense so going to inside your extensions right here well there's an error in mind but just type in tailwind and you will see the first option and install that package great so now that we have that what I want to do is I want to go inside of my globals.css file right here and we don't need to modify anything here but I do want to add one thing so I'm going to add HTML comma body and root and I'm going to give it a height of 100 like this and you don't need a semicolon here great so you did a great job configuring the project it was very fast so what you did was you created a next 13 project with the app router so in the following parts of the tutorial I'm going to explain how to create routes what are route groups and we are going to structure our folders so it fits the architecture of our application great job so far so let's continue where we left off and let's start configuring our application folders so we're going to talk about how to actually create routes inside next 13 application so I'm going to zoom in a bit and inside of the app folder right here I'm going to create a new folder called test like this and inside I'm going to create a new file called page.tsx I'm going to go ahead and I'm going to write a shorthand called test page like this and I'm going to write a div test page exclamation point like this so if you're wondering how do they use that shorthand to create this it's another extension that I have it if it will load for me and it's called Simple react Snippets right here so you can just search simple react Snippets and click install great so I'm going to close this now and I'm going to go back inside of my test page and let's look at this structure so inside of the app folder we have a test folder and inside of that we have page.tsx so the same way we have a page.tsx in the root of our app with folder but this time we put it inside of the test folder so how can we find that route well very simple by going to slash test right here we have the test page so now we know that the way routing Works inside the app folder is by creating new folders and putting a page.tsx file inside of them so that means that every folder we create inside of the app folder will reflect to our URL but what if I want to create a folder which is purely organizational it doesn't reflect the URL well for that we're going to use something called route groups so here we have an explanation which I just said inside the app directory nested folders are normally mapped to URL pads however you can Mark a folder as a route group to prevent the folder from being included in the URL path so that's exactly what I want so the way we write route groups is by creating a folder with parentheses around its name so what I'm going to do now is I'm gonna go ahead and I'm gonna shut down my application because we're gonna change the root route so that can cause some problems in hot reloading so I suggest you shut down the application as well and let's go ahead and let's remove this test page here like this and let's create a new folder here called root like this and inside of the root let's go ahead and create a new file called page.tsx like this and let's go ahead and name it root page like this and a div root page and just put parenthesis protected this is obviously just a text it doesn't mean anything but I want to indicate that this route should be protected by authentication so only logged in users should be able to see this page right here so now that we have this we can actually go ahead and remove this page.dsx because we don't no longer need it so let's go ahead and remove this one and now the only page we have is inside of the root right here so what did we change well regarding the URL nothing because we mapped our folder inside of parenthesis meaning that it's not going to affect the URL and that this page.dsx inside of it is equivalent to a root file meaning that it's gonna be mapped to localhost 3000 slash that's it but let's go ahead and let's create another folder for example I'm going to create a new folder called out so create a new folder inside the app out in parenthesis again because we don't want it to affect our URL and inside let's create a new folder called routes like this and inside of routes create a new folder this time without parenthesis called login like this and inside of that login folder create a new file page.dsx let's go ahead and let's name this login page like this and I'm going to give it a div login page unprotected like this great so what did we do here we created an organizational folder called out and inside of that out we created another organizational folder called routes so inside of here we're going to have login we're gonna have register and inside of this out folder we can have an individual layout which is going to reflect all the routes inside of this organizational folder so you might be wondering well okay but how do we access this login route now well let's start the app and let's see how we access it so I'm going to go ahead in my terminal and run the dev again and I'm going to refresh my page and now since this is on slash test and we remove that I'm probably gonna get a 404 here greatest so I'm just going to remove the slash test and what I should see here is the root page with protected in parenthesis so that is this root folder page.tsx you can see that this root folder name has no effect to the URL that's because it is a route group we gave it a special convention so it doesn't affect the URL so how do we access this login page well let's take a look this folder does not affect the URL because it's in parenthesis the routes folder does not affect the URL either because again it's in parenthesis but the login folder is not inside of parenthesis so all we have to do is go to slash login like this and there we go now we have login page unprotected so we have started structuring our application in the way we want it one thing I just want to change and for that I'm going to shut down my application again so go inside of your terminal and just shut down the application like this what I want to do is I want to create another organizational folder inside of this root folder the same way I did inside of the out so I'm going to create a new folder called routes like this and inside I'm gonna go ahead and I'm going to create a new file page.tsx like this and you can just copy everything from this root page like this so copy that paste it inside save the file and then remove this file there we go so how your structure should look now is inside of the app folder you should have the favicon the globals and layout but your root page will be inside this organizational folder called root inside of another organizational folder called routes and then you're gonna have page.dsx and you can also have out which follows the exact same structure but it doesn't have just page it has a login folder meaning that we do want to map at least one part to a separate URL great great job so let's go ahead and let's run this application to confirm everything it works fine so npm run Dev again I'm going to refresh right here and well we shouldn't see any changes everything should work as normal so on slash login I'm seeing the login page which should be accessible to everyone whether they're logged in or not and on my root page I'm gonna see the root page right here which is inside of our root routespage.dsx which is protected that of course is not working right now because we have not added any authentication so that's what we are going to do next I just wanted to quickly explain how route groups work in next 13 and how to actually create new routes inside great great job so far so the next thing we're going to do is we're going to add authentication using Clerk let's continue and let's add authentication to our project so I want you to go to clerk.com or use the link inside the description if you already have an account go ahead and click on sign in if you don't go ahead and create a new account so that's what I'm going to do after you've created your account you're gonna get a screen similar to this so I'm going to go ahead and zoom in a bit so let's go ahead and let's give our application a name I'm going to give it AI Dash companion like this and you can see that you have 19 available providers sorry more than 19 available providers so if you want to enable your users to log in using their phone number just click this if you want to add the username just click this if you want to add Facebook Apple GitHub Microsoft and other providers you can just go ahead and tick them you also have available in metamask web 3 login if you want to but for my case I'm Gonna Keep the email address and Google login and I'm going to click create application like this great and after that is done you're gonna see a quick start section right here make sure that next.js is selected because that is the application we are working with and in here you have your API keys so before we add these API keys I'm going to go ahead and I'm gonna go inside of my DOT git ignore file right here and I'm gonna go ahead and find Dot environment.local and I'm gonna add another one called dot environment like this great so now that we have that let's go ahead and I'm going to close everything and create a new file in the root of your project called dot environment like this so I know this says to use dot environment.local but we are going to use some other packages like Prisma which require dot environment so let's go ahead and let's copy this keys right here so you can either use the copy button right here or you can click the eye icon and then select everything and just paste it in your dot environment file right here so this is how that should look like I'm going to expand it so you can see it in one line so you should have the next public clerk publishable key and clerk secret key like this great so after that is done make sure again you have next.js selected and just click continue in docs like this there we go so now we have the documentation for clerk right here so what I want to do is I want to go ahead and follow the steps in order to install clerk next.js so let's go ahead and do this first let's run this command npm install clerk next.js so I'm going to copy this command and I'm gonna go inside of my terminal I'm gonna shut down my application and I suggest that you do the same and let's go ahead and run this command npx is sorry npm install add clerk slash next.js and just press enter great once that is done let's see what else we have to do so we have to set our environment keys but we already did that in our dashboard so we can skip this step to set the environment Keys what we have to do next is we have to go inside of our app layout and add the clerk provider so one thing I really like about clerk is that it fully supports the app router as well as the pages folder so it's very rare to see new third-party tools to support the newest app router for next 13. so I'm very happy to be using this because I have confidence that it's going to work in our project so what we have to do now before we start our application again is we have to go inside of our app folder inside layout.tsx right here and we have to import this clerk Provider from ad slash clerk next.js so I'm going to copy this line and paste it here so import clerk Provider from add slash sorry add clerk slash next JS like this and what we have to do is we have to wrap our entire application inside of that provider so let's go ahead and add clerk provider and just wrap your entire application and indent it so it looks better like this great so we have that done but we are still not yet we are not done yet what we have to do now is we have to create our middleware which is going to control which routes are protected and which ones are not so the same place you're added your dot environment file in the root of your project create a new file called middleware dot DS like this so middleware dot DS like that and go ahead and copy this entire uh code snippet right here so what we did is we imported out middleware from add clerk slash next JS and we have a config and we have exported the default out middleware great what we have to do now is we have to create our authentication screens so you might be you might remember that we already started something like that right here so inside of my app folder you can see that I have my out folder here and inside of my routes I have a login page so for now let's go ahead and let's delete this folder right here because we're gonna create uh what our clerk documentation is telling us to do so inside of this routes folder inside of the out folder create a new folder called sign Dash up like this and inside of that folder create a new folder with double square brackets dot dot dot sign Dash up like this so this is another convention in next 13 which will enable clerk to have access to all the routes it needs and inside of this new special folder create a new file page dot TSX like that and you can just go ahead and copy this code and paste it here so this is what it should look like import sign up from clerk slash next JS and Export default function page sign up like this and save this file and now what we have to do is we have to add an equivalent sign in page so for that you can just copy this entire page paste it in this routes and now you should have sign up and sign Dash up copy so rename this sign up copy to sign Dash in like this and inside of it you also have to rename this special folder to be signed Dash in like that and go inside of its page folder and obviously we have to modify this now to use sign in so let's go ahead sorry so right here it says build your sign in page so instead of importing sign up we are importing sign in and instead of returning sign up we are returning sign in like this I have an error here but that is just a visual studio code error so what I did was I pressed command shift and P and I wrote reload window and I pressed enter so that's what resolved the visual studio error great now that we have that done let me just confirm that you have the same folder structure that I do so inside of the out folder inside of the routes we have sign in and sign up and they follow the exact structure so double square brackets dot dot dot sign Dash in with the page.tsx exporting sign in and another one sign up with the equivalent special folder exporting sign up great and now that we have that done we have to add some more variables to our environment so go ahead and copy this I'm going to close everything here I'm gonna go inside of dot environment here and I'm going to paste this right here so make sure you have the sign in url sign up URL and after signing URL and after sign up URL so how do we know that this is the correct value for this environment key and same for this one well very simple because this is what we named our routes so let's go ahead and let's repeat our lesson the out folder does not reflect the URL neither does the routes but sign in does so this route is equivalent to slash sign in and this one to slash sign up so that's why we've write this here and what these two routes do is they control where do we redirect after user has been successfully logged in and if you remember in my root folder in my page I've wrote that this should be protected meaning that this is correct in my environment file after I'm authenticated I'm gonna go to the protected page which is our root page right here great so make sure you save this as well and let's go ahead let's start our application now to see if this is working so I'm gonna go inside of my terminal here and I'm gonna go ahead and run npm run Dev great I'm gonna go back and I'm gonna refresh this and let's see what will happen now that we have added clerk inside so what happens is that we are immediately redirected to a sign in page but something is wrong with the design here I want this to be in the center so let's take a look at our structure what can we do here so inside of our app folder we have an out folder and inside we have routes so we could either go individually in both of this page.tsx create a div around this and add a flexbox to center it or we can do something better since we are using the route groups one cool thing about them is they don't affect the URL but they can share the layout between all of their routes so inside of the out folder create a new file called layout.dsx and let's go ahead and let's use the shorthand uh out layout like this the props it's going to have is children like this and let's go ahead and give the type for children to be react.react node like this and just go ahead and create a dip with children like this and once you save you're not gonna have any errors so let me repeat how this looks so inside of my out folder I created a new file called layout.dsx so I didn't create it inside the routes only inside of the out folder but this layout is going to reflect to all of the routes inside so let's go ahead and let's give this div a class name of flex items Center and justify Dash Center and H dash full and look at this all of a sudden both of my routes both sign in and if I click on sign up both of them are now centered isn't that amazing so that's how cool these route groups are we are able to create one layout file and they reflect everything inside of that folder I mean all the routes inside of that folder great so now let's go ahead and let's use continue with Google to create an account for this application and after that is done we are redirected to the root page which is protected great and now I want to show you a really cool component clerk has called user button so it's this cool component which has options for the user so let's go ahead and let's go inside of our route inside of page.tsx so inside of root routes page.tsx right here and instead of writing the root page let's go ahead and let's use the user button which you can import from ad clerk slash next JS like this and just save this let's go back and look at this now I can go ahead and click here and I can manage my account I can change my password I can change my email I can delete my account I can change my profile image I can remove one absolutely amazing and one more thing we have to add before we test the sign out is a prop called after sign out URL so after we sign out I want to go to slash like this great so let's go ahead and refresh this application and let's try to sign out so I'm gonna go ahead and click sign out right here and you can see that I'm immediately redirected back to sign in and what if I try to manually go to localhost 3000 you can see it doesn't let me that is because we have successfully protected our root page so now what was just texted before is actually working great so make sure you're logged in go ahead and enter and you should see your logged in icon right here that's how easy it was to add authentication using clerk you can go ahead and go inside of the dashboard of Clerk and you can play around you can see that it has recent signups recent signings you can see all about your analytics here you can play around with customization you can add your logo you can change your name you can add icon whatever you want you can also go ahead and control all of your users you can manually remove them you can ban them you can even impersonate them so a lot of cool things that clerk offers great great job so far and now that we have our authentication working it's time to put this amazing little button inside of a navigation bar I think that would look much much better so in The Following part of the tutorial we're going to start creating our layout with our sidebar and our navigation bar so let's continue and let's create our navigation bar so I'm going to go ahead and I'm gonna close everything I'm gonna go inside of my app folder inside root and I'm going to create a new folder inside this root folder sorry new file called layout.dsx so the same way we added layout in our out folder right here and you can see right now we have an error because our layout is recognized by next 13 but we have not written any code inside so let's go ahead and let's write root layout like this let's give it a proper children and let's go ahead and let's add types for that so children is going to be react.react node like this and we're quickly just going to go ahead and return a div and render children inside like this and now our error has gone away great so what I want to do now is I want to give this a class name of H dash full like this and just above this children I'm gonna go ahead and add a main component like this sorry a main element and I'm going to wrap the children inside of that I'm gonna go ahead and I'm gonna give it a class name of mdpl-20 like this and I'm gonna give it a pt-16 like that and I'm gonna give it an H dash full like this great so just make sure you're not too zoomed in and now you can see that uh my root my entire root page and this little button has space on the top and space on the left that is because this place on the left is going to be our sidebar and here at the top we're gonna have our navigation bar so first let's go ahead and let's do our nav bar so I'm going to name a component like this but of course if I save we're going to get an error because navbar does not exist so let's go inside of this components folder which is currently empty if you don't have it you can just create it but it should have it should be here because chat CN command added it so let's go ahead and create a new file inside called navbar.dsx like this let's go ahead and let's mark this as use client like this and let's write export const nav bar like this and let's just return a div same navbar like that great and now that we have this we can go back inside of our root layout so inside the root folder uh sorry inside the root for the layout right here and go ahead and import that nav bar from add slash components slash navbar like this and now you can see that we have a space for our nav bar here and our root content is inside great let's go back inside of this nav bar and let's go ahead and let's give it some class names so I'm going to expand this as much as I can so it still says in the desktop mode so class name here is going to be fixed W Dash full like this Z is going to be 50 meaning Z index is going to be 50 it's going to have a flex justify Dash between like this items Dash Center like that is going to have ey 2 is going to have bx4 like this it's gonna have a bottom border and that border is going to be Dash primary slash 10 so we're gonna give it a little bit of opacity and BG is going to be secondary like this great and now that we added that inside I want to go ahead and remove this and create a new div with a class name of flex items Dash Center like this and inside I'm going to use the menu icon from Lucid react like this but for now I'm going to give you the class name to be MD hidden like this but it's going to be blocked on small devices meaning that if I go to an extremely small device it's going to appear right here but if I'm on desktop no need for that so that's going to be our toggle for the sidebar on mobile but on desktop sidebar is going to be visible so no need for that little I on right here so make sure that you are in a large screen so you should not be you should not be seeing this little icon here great so just expand so you don't see it great now that we have this let's go ahead and let's add our link component from next slash link so I added that import right here great let's add this link an href to the slash like this and inside let's go ahead and let's add an H1 element which is going to say companion dot AI like this great you can see how it appears right here now let's go ahead and give this a class name and let's go ahead and give it a hidden class name but on medium devices is going to be visible so what happens now is that when I Collapse you can see that we can see the toggle button but we cannot see the title but when I expand we can do the opposite of that so make sure you are seeing this companion AI so just expand your screen a little bit let's go ahead and let's continue giving these classes so we're going to give it text Excel like this so larger text on medium devices is going to be text-3 Excel like this it's going to have a bold font like that and the text is going to be primary like this great what I want to do now is I want to change the font of this text right here so I'm going to go ahead and I'm going to write a constant font to be Poppins which you can import from next slash font slash Google like this go ahead and open parenthesis open an object and give it a weight of 600 because we are only going to use the Bold abbreviation of this phone and subsets are going to be an array and Latin like this great so now let's go ahead and let's modify this class name for this H1 element a bit first thing I'm going to do is I'm going to wrap it inside of curly brackets like this so that should not change much but what I'm going to do next is I'm going to use CN util which we have inside of our lib folder utils right here so we're going to use this CN function and I'm going to explain what it does in just a second if you're wondering where did this come from it came from chatsy and latest you need to command great so go ahead and import this CN from add slash lib utils right here so I added CN from add slash lip utils and I'm going to keep this Imports separated from the global ones great so let's go ahead and let's open parenthesis and close it right here so our CN library now wraps the entire class name like this and nothing should change yet but the way C and Library works is that in the first line so this is all one line right so I'm going to expand this so you can see in the first line sorry in the first prop of this function it accepts the class name the initial class name but in the second one it can accept Dynamic class names so these class names can be dependent on whether this is active on anything you want or it can be something like this phone dot class name like this so this is the proper way to add a dynamic classes to Tailwind because this util uses Tailwind merge and clsx which which ensure that there is a proper way of merging our classes and there are no duplicates or overwrites great so just go ahead and add the CN Library make sure these are the default classes of this H1 element and then add a comma and for the second parameter of this CN function add font dot class name this font constant comes from the Poppins right here so basically what we are doing is we are saying these are the main classes I want you to have but I want you to have one Dynamic class name called font dot class name right here so now when you expand you can see how we have a cool font right here perfect so that is exactly what I wanted to do so now let's go ahead and let's go outside of link and outside of this div element here and create a new div element let's go ahead and give it a class name of flex items Dash Center and gag Dash x-3 like this and inside you can go ahead and add user button component from add clerk slash next JS so just go ahead and make sure you've imported user button from ad clerk slash next JS and I'm going to move it with this Global Imports right here great so now you can see that I have this in my nav bar which is exactly what I wanted so we actually don't no longer need it inside of our root page but it's fine we can leave it for now great what I want to do now is I want to add a couple of more elements to our navigation bar so let's go inside ui.chatcn.com so I want you to be on this page every time I am so you can either Google Chat CN UI and click on the first link or you can go to ui.chat cn.com and what I want you to do is I want you to click on the components and I want you to find the button component right here and let's see which command we have to run in order to add this button component to our application so we have to run this command I'm going to click copy and click npn after that I'm heading into my terminal right here I'm going to close my app and I'm going to run the following command npx chat cn-ui at latest add button and just press enter and then go ahead and press y to confirm this installation after the button has been installed you can go ahead and run your application again and if you've shut down your app and now you run it again make sure you refresh it right here great so what I'm I want to do now is I want to add one special button to our navbar right here so inside of this div which holds the user button we're going to add a new button which you can import from dot slash UI button so pay attention to how I import it right here but I'm gonna change this to add slash components you don't have to do this so this is completely fine but I like to be consistent in my imports so I'm going to use this Alias right here every time I import something great so now we have our button right here and let's go ahead and let's write upgrade right here let's go ahead and let's add sparkles from Lucid react so this is going to be a self-closing tag which is an icon and I imported sparkles from the same place I imported menu from Lucid react right here great let's go ahead and let's give this Sparkles a class name so I'm going to give it h-4 W-4 fill Dash white text Dash white and margin left 2 like this great so now it looks a bit better now let's go ahead and let's give this button a size of small like this great and what I want to do now is I want to show you how cool this chat CN UI library is so it looks like we just added a button component and now we can import it so that's nothing new you've probably seen this in Chakra in material UI in a bunch of other uh component libraries but what's really cool about uh a chat cnui is that this button is actually inside of our project so inside of our components folder inside of UI folder we have the entire code for our button component that means that we can modify it however we want so let's go ahead and we have these variance right here and let me show you how you can use them so in my navbar right here I can go ahead and write the variant right here and let's try it destructive and you can see how now it became red you can play around with ghost you can play around with secondary so you can play around with a lot of stuff here right but we're gonna go ahead and we're going to create our own variant which is going to be called premium and let's go ahead and let me show you how we can do that so let's go back inside of our components folder right here inside UI inside button.dsx and inside of this variance just after link I'm gonna add a new one called premium like this you can name it whatever you want it doesn't matter but I think premium suits it just fine so I'm going to go ahead and expand this a little bit and I'm going to write BG Dash gradient Dash 2 Dash R meaning gradient to right from sky-500 which is a specific color I want via blue dash 500 to cyan 500 text is going to be white and border is going to be 0 like this great so I'm going to expand this a bit back I'm gonna go inside of my nav bar again and let's try if I type variant now you can see that my autocomplete is actually gonna say premium right here so if I click premium there we go look at this look at how beautiful this this button looks now so that's exactly what I want to add here great what I want to do now is I want to show you how to add a dark mode and light mode toggle switcher to this project so let's do that now so what you have to do is you have to go back inside of chat cnui right here and you're gonna see dark mode in this getting started section here so click on the dark mode and let's see what we have to do in order to add that so first thing that's actually not written here is that we have to install a package called the next Dash themes so let's go inside of our terminal I'm going to shut down my project and I'm going to write npm install next Dash teams at latest like this so let's go ahead and let's save this great and now we can run our project again I'm going to close the terminal and if you close if you close your app just make sure you refresh it again after you run npm run Dev so it's up to date otherwise you're not going to see your changes in real time until you refresh great so what we have to do is we have to create a theme provider so let's do that let's follow these instructions so it says instead of our components folder create a theme provider let's do that I'm going to go inside of components and I'm going to create a new file team Dash provider.esx like this and I'm gonna go ahead and copy this entire code and paste it inside great so you can see how that looks right here perfect now that we have that code we have to wrap our root layout inside of that code right here so let's go inside and let's go inside of our app folder inside of layout.dsx right here and let's go ahead inside of our body right here so I'm going to go ahead and I'm going to give space here and I'm going to give space here so make sure you are inside of the body and you're going to add the theme Provider from add slash components slash team provider make sure you don't accidentally import it from next themes and just wrap your entire children inside of this theme provider so this is what I imported my theme Provider from add slash components team provider make sure your import is the same and I'm just going to separate my imports like this great what we have to add here now is an attribute called class so let's add attribute class let's give it the default theme to the system and let's say enable system at the end so these are the three attributes which we need attribute class default theme system and enable system great so my system is in dark mode and that means that my default class name is actually going to be dark but let's go ahead and let's continue here so I'm going to go oh one more thing before we move on let's also add this suppressed hydration warning right here so I'm going to copy this and I'm going to add it to my HTML element right here because this theme provider can actually cause some hydration warnings which won't actually affect anything in production but they are very annoying to see in development great now let's go and let's add this mode toggle button so you can see how that is going to look it's going to look like this so what you can get do is you're going to go ahead and close everything you're going to go inside of your components folder and create a new file called team Dash toggle dot DSX like this go ahead and go inside of this code right here go ahead and copy this entire code and paste it inside like that what we are missing is the drop down menu so we have not added that so let's go ahead and let's see how we can add it I'm gonna go ahead and I'm gonna click components here and I'm going to go ahead and find the drop down menu right here let's see how we can install it so npx chat cnui latest add drop down menu and let's add that right here let's go inside of our terminal I'm gonna close everything I'm gonna write this Command right here so npx chat cnui has latest add drop down menu let's go ahead and let's confirm the installation and let's just wait a couple of seconds for this to install after that has been done we can go ahead and run npm run Dev again so npm run Dev and you can see how my error has been resolved because I successfully added this drop down menu to my project great so go ahead and save this theme toggle right here actually it's named the mod toggle so let's go ahead and just rename this component here so instead of Team toggle let's name it mod toggle like this great and just save everything inside once again perfect so what I want to do now is just refresh my application and I'm going to go ahead and I'm gonna go you can see how my theme just became dark so it doesn't matter if your didn't that's because I'm using dark mode in my system so it read from my system so don't worry if yours is still in light mode here is how to manually change so go inside of your components inside of navbar right here and where we added our button Sparkles and our user button go ahead and add the new mode toggle button like this and you can import that from dot slash mode toggle org from slash components mod toggle like this there we go and you can see how you now have a button for that right here perfect so let's go ahead and try light now and now it's light if I click uh on dark now it's dark and if I click on system it's going to read what is default in my system perfect so you can see how cool this looks but I want to modify this a little bit so I don't want this to be so visible as a button so what I'm gonna do is I'm gonna close everything I'm gonna go inside of components mode toggle so this component which we just copied I'm gonna find the button and I'm going to change the variant from outline to secondary like this great so now it's not as visible that it is a special button and I think it just looks a bit better perfect great this is exactly what I want and one cool thing I want to show you is that if you don't want to enable your users to switch between light and dark mode you can go ahead inside your app folder inside layout find the theme provider and there is a special attribute which you can use so instead of default theme you can use forced theme and pass in dark for example and now everyone on your website is going to have dark theme regardless of what is under system or what they chose so great I'm not gonna use that so I'm going to use the default theme system like this but you can go ahead and play around if you want just a dark theme uh great perfect so we have successfully added our navigation bar what we have to do next is we have to actually enable this sidebar so that's what we're gonna do next great job so far let's continue by creating our sidebar so from now on I'm gonna be I'm Gonna Keep developing in dark mode I just want to point that out so you can choose dark mode if you want to have the same results that I am it's just easier on my eyes than looking at the light mode all the time great so let's go ahead and let's go back inside of our root layout right here so I'm gonna go inside the app Root layout right here and just below this nav bar right here we're going to add a new div and we're going to give it a class name of a hidden on small devices but on medium devices it's going to be 8 flex and it's gonna have a margin top of 16 like this it's going to have a w-20 flex Dash call fixed and inset Dash Y dash zero like this and inside of this div we're going to create a new component called sidebar like this so just save this and of course we have an error because sidebar does not exist so let's go ahead and let's go inside of our components folder and create a new file sidebar.dsx like this let's go ahead and write export con sidebar and let's just write sidebar like this great let's go back inside of our layout where we have the sidebar and let's import that from add slash components slash sidebar and save and there we go now we have space for our sidebar right here perfect now let's go ahead and let's mark this entire sidebar component as use client like this and let's go ahead and let's give it some Styles so through this div I'm going to write a class name right here space Dash y-4 Flex Flex slash call like that H dash full text Dash primary and BG Dash secondary like this great now let's go ahead let's create a new div inside of this wrapping our text like this with the class name which is going to be d-3 flex-1 and justify Dash Center like that great and instead of this text sidebar let's create a new div with a class name space Dash Y dash 2 like this and in here is going to be our routes so we don't have them yet but we're gonna have to have them soon so you might notice that we have a little bit of space between our sidebar and our navigation bar so let's quickly fix that so I'm going to go back inside of my nav bar so in components and I'm gonna give it another class height 16 like this great so now it has a fixed height so you can see that our content is perfectly spaced from the sidebar and from the navigation bar and our sidebar perfectly aligns with it great let's go back inside of our sidebar now and what we have to do is we have to create some routes so let's go ahead and let's write const routes is an array which has objects inside first one is going to have an icon of home from lucidreact it's going to have an href to be slash it's gonna have a label home like this and pro is going to be false like this so Pro is what we're going to use to determine whether a route should be protected or not let's copy this two more times like this let's give this second one A Plus icon from lucidreact let's give it an href of Slash companion Slash new and a label of create and pro for this one is going to be true great for the last one let's use the settings icon from Lucid react so I imported home plus and settings from Lucid react let's give this a slash settings href and label settings like this and pro is false great now we're going to use these routes to iterate over them right here so let's go ahead and let's do that so instead of writing routes we're going to go routes.map we're going to get the individual route and we're going to immediately return a div let's go ahead and let's give this div a key of route.href like that and let's give it a class name CN from add slash lib slash yields so make sure you import at lib slash utils I'm just going to separate this to Imports great go ahead and open parenthesis in this CN right here and let's first give it a default class so I'm going to write text Dash muted Dash foreground like that text Xs group Plex p dash 3 W Dash full Justified Dash start like this font Dash medium cursor Dash pointer hover text Dash primary like this I'm also going to give it a hover VG Dash primary slash 10 rounded LG and transition like that great and what I'm going to do for the dynamic route so I'm going to add a comma after this big class right here and I'm going to write if pad name which we have to import so const path name is equal use path name like this and you can import that so import use pack name from next slash navigation like this so now that you have the path name you can write its path name is identical to route.href then we're going to add a dynamic class name bg- primary 10 and text primary like this great and now let's go ahead and inside of this div we create a new div with a class name of flex Flex Dash call like that Gap Dash Y dash 2 items Dash Center and flex dash one like that and inside I'm going to render route dot icon which is a self-closing tag and I'm going to give it a class name of h-5 nw-5 like this so you can see how we now have icons right here and all that's left to do is to add a label so route dot label like this there we go so you can see how now we have home create and settings so let's just quickly fix the fact that settings is a bit is looking a bit weird so what I forgot to do is add one class name so outside of this routes.map outside of this div right here in this div which begins with p-3 alongside flex-1 we also need to add flex and now you can see how it fills up the entire sidebar great perfect what I want to do now is I want to enable that once we click on these routes we actually navigate to them so let's go ahead and let's create our constant on navigate so const on navigate it's gonna take the URL which is a string and it's going to take row which is a Boolean like this it's going to be an arrow function like this and the first thing we're going to do is add a to do check if Pro so right now we don't have to check anything we're just gonna do uh return and now we have to add our router so const router is equal to use the router from next slash navigation make sure you don't accidentally import router from next slash router that's the wrong import so now that you have the router right here you can go ahead and write a return router that push URL break and now let's go ahead and use this on navigate in this div which has a key of route href and right on click an arrow function on navigate Route Dash URL sorry Dot href and Route dot Pro like this so it passes two arguments like this and now if I go ahead and click on create uh I should be redirected to a 404 page because it doesn't exist yet and same for settings but it should at least do something perfect now what we have to do is we have to enable the sidebar even when it's on really small devices like this so let's do that now so in order to begin first we have to go back inside of chat cnui and we have to add a new component so I'm going to expand this and go ahead and find sheet component right here so the command we have to run is MPX chat scene UI at latest as sheet so let's go ahead and copy this let's go inside of our terminal right here and let's go ahead and run this command so npx Shad cnui latest add sheet like that and just press enter and after that just go ahead and confirm the installation wait a couple of seconds and after it's been done let's run the project again go back and refresh your entire project great now that we have that let's close everything let's go inside of components inside navbar.tsx right here let's go ahead and let's replace this menu icon right here so while you're developing this just make sure that you're in Mobile mode so you should be seeing this menu icon here and you should not be seeing the sidebar so replace this menu with mobile sidebar like this it's a self-closing tab like that and of course it's going to cause an error because we don't have mobile sidebar yet so let's go ahead let's go inside of components create a new file mobile sidebar dot ESX like this let's go ahead and write export const mobile sidebar like this and just return a div saying mobile like this great now you can go back inside navbar and import the mobile sidebar from dot slash mobile sidebar or components mobile sidebar like this and the error should go away and you can see how now it says Mobile in the dashboard sorry in the navigation bar right here so let's go back inside mobile sidebar and let's actually see what we have to do here so let's go ahead and let's import the menu icon from Lucid react and now let's go ahead and let's import the sheet sheet content and Sheet the trigger like this from add slash components slash UI slash sheet like this and let's also import the sidebar from dot slash sidebar so our actual sidebar component and I'm just going to replace this to be add slash components like this now that we have that let's replace this div with sheet like this let's remove this mobile text and let's write it shift trigger like this let's go ahead and write menu inside like this now let's go ahead and write class name MD hidden pr-4 like that and let's go ahead and let's add sheet content below that so outside of sheet trigger let's add sheet content and write sidebar like this and just render it it's a self-closing tag give it an attribute side of left and a class name of p dash 0 BG they are secondary pt-10 and w32 like this so now if you go ahead and click you can see how we have a working mobile sidebar great great job so now you have a fully working desktop sidebar which has a working navigation but we just don't have the routes yet and you have a fully working mobile sidebar for mobile devices great great job you're doing an amazing job so far let's continue and let's create our search input right here which is going to stand above our categories so in order to do that first we have to add an input component from chat CN so go to chat CN and go ahead and find the input right here and in here you can find the CLI command to install it so let's go ahead and let's copy this in an npm format like that I'm going to expand this so you can see my terminal I'm going to shut down my application and I'm going to write MPX chat cnui at latest add input so go ahead and press enter confirm your installation and just go ahead and run npm run Dev like this great after we have that let's go ahead and let's refresh our application and what I want you to do is I want you to go back inside of your app folder inside root inside routes page.tsx right here great and you're actually going to remove this user button for now and let's just write root page like this great so you can remove this import as well perfect and now that you're inside of here let's go ahead and let's give this div a class name of H dash full b-4 and space Dash Y dash 2 like this great so now our root page has a bit of indentation like this you can see how it's evenly spaced from the top and from the sidebar right here great now instead of this staying root page it's actually going to say search input like this and if you save of course we are going to get an error because search input does not exist yet so let's go ahead let's go inside of our components and create a new file search Dash input.psx like this great let's mark this as use client like this and Export const search input like that and return a div saying search input like this great now go back to the root page and let's import this search input from add slash components search input just like this great now that we have this search input let's go ahead and let's add another package which we are going to need so I'm going to go ahead in my terminal I'm going to close everything and I'm going to write npm install query Dash string like this so go ahead and press enter and then run your application again and after you've done that make sure to refresh your localhost great now let's go ahead and let's actually style this so I'm going to go ahead inside of my search input component so go inside components search input right here let's go ahead and let's give this div a class name of relative like this and instead of this saying search div we're actually going to add a search icon sorry search like this from Lucid react so it's going to be a self-closing tag let's give it a class name of absolute h-4 W-4 top Dash 3 life-4 and tax Dash muted Dash foreground like this great and below that we're gonna add our input from dot slash UI input like this so you can replace this with Slash components UI input if you want to follow my import Style great now let's go ahead and let's give this some prompts so we're going to go ahead and give it a placeholder of search and three dots like that and let's go ahead and give it a class name of pl-10 and BG Dash primary 10 like this great so you can see how this looks much better now before we continue I just want to fix the background of this right here so you can see that it's dark black and I want it to be BG secondary like this so in order to do that what I want to go is I want to go and close everything I'm going inside of my app and inside of my main layout right here and I'm gonna go ahead and add another class name to this inter class name so for that I'm going to use the CN Library so let's go ahead and import CN from add slash lib utils like that and let's go ahead and open parenthesis like this let's go ahead and close them like that and for the first class let's go ahead and write BG Dash secondary and for the second enter dot class name like this and there we go now it looks much better so now I'm going to close everything again and I'm going back inside of my app folder sorry instead of components search input right here and now we're going to go ahead and make this search input functional great so what we have to do now is we have to add a couple of uh a couple of hooks here a couple of states and a bunch of other stuff we need so constant router is equal to use router from next slash navigation cons search params are equal to use search params from next slash navigation and I'm going to put that at the top right here great now besides having a search for Rams from the search input we are also going to be able to choose categories if we remember from the demo and intro of this video so what I have to do is I have to fetch the category ID from the URL even though it doesn't exist now even though we don't have any code for it we have to prepare for the fact that in the future in our URL there will be a specific query controlling the currently loaded category so that's why I'm going to add const category ID to be search params.get category ID like this great and now let's go ahead and write const name to be search params.get named so this category ID is actually not going to be controlled or assigned in this component it's going to be assigned in the component we are going to create next which is going to be those little boxes here which are going to each have its own category but what we are going to control is the search program called name so let's go ahead and let's write const value set value you state name byte pipe empty string like this and let's also import use state from react like this and I'm going to move that to the top as well great so now that we have that what I want to do is I want to add a debounced version of this value so I don't want to query my database every time I type something in here I want to add a debounce so that only after I type and the millisecond passes or a full second passes that's when I want to do the query to the database so I don't want to make too much requests on every single keystroke change so in order to do that we're gonna have to go and create a use debounce hook so let's go ahead and let's create a new folder in the root of our application so I'm going to create a new folder called hooks like this and inside I'm going to create use dash debounce dot TS like this great let's go ahead and let's import use effect and use state from react like this and let's export function use the bounce let's give it a generic T like this let's give it the value of T delay optional which is a number return value is T as well and let's just write const the bounced value set the bounced value the b u state value like this and type of view state is going to be T again great now that we have that let's go ahead and write use effect like this let's go ahead and pass in value and delay inside of the dependency array of this use effect function and let's write cons timer to be used sorry set timeout open an arrow function inside and write a set debounced value to the value and comma delay Pi pipe 500 like this and in the return function let's go ahead clear timeout timer so there is no overflow if this accidentally gets unmounted great so this is what this looks like so in our use effect we create a timeout function which sets the debounced value every time our value changes and it uses either the past Delay from the props or it uses a default 500 or half a second here great and now let's go ahead and return the bounced value like this and now we should have no errors inside great so now that we have this important hook if you have any problems with this if this is complicated you can always visit my GitHub and just find this exact file in hooks USD bounce and just paste it inside great so I'm going to go back inside of my search input now and what I'm going to do is I'm actually going to go ahead and write const the bounced value to be use debounce from add slash hooks ESD bounce like this I'm gonna pass in the generic to be string like this and I'm gonna give it a value and a delay of 500 or you didn't have to do it you can just pass it like this but let's be explicit and pass in 500. so what this is going to mean is that our actual value which we are going to use to query the database for what we search is only going to be triggered every half a second uh so not every single time we trigger a keystroke only after we stop triggering the keystroke for half a second we consider that as a finished typing from the user great now let's go ahead and let's write const on change to be change event Handler from react like this let's pass in HTML input element like that then we get the event and let's go ahead and just set value event.target.value like this great now let's go ahead and let's create a use effect inside and this use effect is going to react on every debounced value change but it also needs a router and category ID inside great so first let's define the current query so const query is going to be a name which is debones the value and category ID which is category ID and we have this category ID right here in this search params perfect so we have the query which we want to pass to the URL now let's go ahead and write const URL is equal to Qs and we have to import Qs so go ahead and write import Qs from query Dash string like that so now that you have that go ahead and write qs.stringifyurl open an object and write URL to be window.location dot href like that and category sorry and query like this or you can write query query so you can use the shorthand if you want because the key and the value are exactly uh named exactly the same now in the second argument in here I'm going to add some options so I'm going to write skip empty string to be true and Skip know to be true as well what this is going to do is it's going to remove any of the query so for example if category ID is equal to null if it's equal to null or if it's equal to an empty string it's going to remove them from the query and it's going to do the same thing from name great and now all we have to do is router.push URL like this great now let's assign this to the input so in this input I'm going to go ahead and write on change to be on change and value to the value like this great let's go ahead and let's test this out and see how it works so pay attention to my URL right here I'm going to go ahead and write test and you can see that it actually added a query with a name and my value tested like this if I move it back now it's test and you can see that I can type it but it's not immediately changing only after I stop typing it's adding to the URL so that's why we did that debounce thing if we didn't do the debounce thing it would react on every single keystroke change and that's just way too many queries to the database we don't want to query the database that much so that's why we added an optimized debounce so only after I finish typing will the URL change as well great great so you finished the search and now we're gonna go ahead and create our components now let's go ahead and let's create our category selection right here in order to do that we're gonna have to initialize Prisma and connect to Planet scale mySQL database so first things first let's initialize Prisma I'm going to go ahead I'm going to collapse everything and I'm gonna go inside of my terminal right here I'm going to close the app and I'm going to write npm install capital D so so Dash capital D Prisma like this wait a couple of seconds for it to install and then you're gonna run another command so let's go ahead and let's run MPX Prisma in it like this wait a couple of seconds and there we go let's see what was added in our project so as you can see now I have a Prisma folder with schema.prisma inside and I also got something new in my DOT environment right here so you can see it did not override everything we had in my environment but it did add this by this database URL and you can see a comment this was inserted by Prisma in it and you can get more information about that here great so in this database URL we have a dummy fake postgrad SQL connection and in our schema Prisma we have a provider postgresql as well so obviously we're gonna have to change that so what I want you to do is I want you to go to planetscale.com or just Google Planet scale and click the first link and go ahead and create an account after you created your account you're going to see a screen similar to this so I'm going to go ahead and I'm going to click on this create button so ready to create I'm going to click here great now let's go ahead and let's give our database a name so I'm going to call it AI Dash companion like this let's zoom out and you're going to go ahead and select this hobby which is a free database like this so make sure you select the free one no need to pay for uh just a tutorial great you can leave everything else exactly as it is so give your database a name select hobby which is free and go ahead and click create database and make sure that it says total monthly cost free right here so no need to pay anything for the tutorial and just click create database and now you're gonna have to be a bit patient so I'm gonna pause the video and I'm going to show you how this looks once it is initialized so you can take some time so just be patient and when you get the exact screen that I get after I unpause the video you all know you're ready to continue great so after you see this prompt right here ready to connect to your database or this option get connection streams it means that it's ready so let's go ahead and I'm going to click ready to connect your database right here and here I need to complete this model to create a password so you can just leave everything exactly as it is and just click create password like this and now make sure you select connect with Prisma so you have a lot of options here but we are going to use Prisma such make sure you select that so first let's go ahead and let's copy this which needs to be put in a DOT environment file so go ahead and just click copy or select everything and copy and let's go inside of our environment file and I'm going to remove this which was inserted by Prisma and I'm just gonna paste the new one from planet scale right here so just make sure it's database underscore URL it needs to match this environment right here database underscore URL so make sure that matches great and after you copy that you can go ahead and select this option schema.prisma in this model right here and again you can just copy that entire thing and that goes in schema.prisma so let's go ahead and let's go there I'm going to go inside of my schema.prisma I'm going to select everything and I'm gonna paste here so you can see our provider has changed to mySQL our URL will stay the same and relation mode Prisma was added great and you can now go ahead and close Planet scale perfect what I want to do now is I want to create a model category so model category like this it's going to have an ID which is going to be a type of string it's going to have an ID and it's going to have default uuid like this great we're also going to have a name which is a type of string as well and that's going to be it for now like this perfect so just make sure you have a model you also don't need to put this many spaces you can just do this but I like to keep them all in the same line perfect so now that we have this what you need to do every time you modify your schema Prisma is you have to go inside of your terminal right here and you have to write npx Prisma generate so this is going to add it to the node modules right here so it's going to read from from schema.prisma it's going to read this new model which we just added and after we run generate we we have to run npx Prisma DB push so generate additive to our local Prisma so we can work with it and MPX Prisma DB push is going to add it to Planet scale great so both of these were a success to me so what we have to do next is we have to create our database a library so let's go ahead and let's go inside of our lib folder right here and create a new folder called Prisma DB dot DS like this so we're going to use this library inside of our routes inside our server components basically anything that can access the database let's go ahead and import Prisma client from at Prisma slash client like this now uh I'm not sure if you have this installed so I'm not sure I installed it either so I'm gonna go ahead and just in case I'm going to run npm install at Prisma slash client and make sure you do that as well so it's not throwing any errors for me it could be that one of the commands in Prisma in it installed me but just in case I'm gonna also manually install that so now I have ADD Prisma slash client make sure you do those steps as well so you don't have any errors and now we have to write a special function inside which is going to prevent the next 13 hot reloading from causing any problems with initializing this Prisma client so for that we're going to write const Prisma DB to be Global this dot Prisma and for now you can ignore this error we're gonna fix it very quickly and I'm going to explain why it happens so Global does the globalbis.prisma pipe pipe new Prisma client like this and then just go ahead and write if process.environment dot node environment is not identical to production in that case Global desktop Prisma is equal to Prisma DB like that and just export default Prisma maybe like that and make sure you don't do a typo in the export like this so how do we fix this error well very simply the problem is that Prisma doesn't exist in our global object so we have to declare Global and add the Prisma model to it and we can do that simply by doing declare Global like this and add VAR Prisma to be Prisma client whoops pretty smart client or undefined like this there we go no more errors perfect what I want to do now is I want to find a way to insert these categories to our database so the way we're going to do that is we are going to create a script so go to the root of your project whoops to the root of your project and create a new folder called scripts like this and inside of this scripts folder create a new file seed dot DS like this go ahead and write const risma client from sorry const Prisma client is able to require at Prisma slash client like this so you might be noticing that we are not using import we are using require that's because this is in pure node.js so this has no relation uh to our react or to any of our environment which next 13 uses this is a complete node file so this will have no relation to any components or anything inside so that's why we have to write it in a different way and now let's write const DB is equal to new Prisma client like this and let's write a synchronous function Main to be a try and catch block so let's resolve the catch which is going to have an error and we're just going to console log sorry console.error error seating default categories like this and just log the error like that great and finally we are going to call awaitdb Dot dollar sign disconnect like this great and in the try function I'm going to go ahead and insert categories in my database so I'm going to write awaitdb.category like this dot create many like that and I'm going to write data to be an array like this and it's going to have some objects so my first category is going to be famous people you can modify this to whatever you want but I'm going to insert a couple of categories here so my second one is going to be movies [Applause] TV my third one is going to be musicians like this then I'm gonna have some games then I'm gonna have animals then I'm gonna have philosophy we lost so three like that and then I'm gonna have scientists like that perfect so how do we run this script now well before we run it we actually have to call it in the script so make sure at the end of your file you do mean like this no need to export anything inside so again we're not going to use this in any of the components and it doesn't have access to any of the components great so what we're going to do now is we're going to go inside of our terminal right here and before we do anything I want to show you how to look at your data in MySQL using Prisma so go ahead and write MPX Prisma Studio like this that's going to run the studio on localhost 555 so I can go ahead and go here and you can see that since we run the MPX Prisma DB push and empty express my generate I have all models category here and you can see that currently we have no category available so I'm gonna go ahead and I'm gonna open another terminal and I'm gonna go so keep your Prisma Studio running in one terminal open another terminal and what we're going to do here is we're going to write node scripts slash seed dot DS like this so just run this and there you go no error on my side if you got any error just go ahead in my GitHub and confirm that your code is exactly the same as mine perfect and how do we know that we go to scripts slash seed well that's because we are in scripts and seed right here so what we've done now is we created a bunch of categories and you can see when I refresh my Prisma Studio here I have philosophy animals movies musicians scientists famous people and games perfect so now that I have this in my database I can actually use that to load them in here so let's do that now so let's go ahead and let's close everything inside and one important thing let's make sure our application is running so I'm gonna close uh both of my terminals right now no need to longer have your Prisma Studio but you can if you want to but I'm going to use this to run npm run Dev again so this is the command that I run npm run Dev and I'm going to refresh my Local Host right here so where I have to go is inside of the app folder inside a root right here inside routes page.tsx right here and what I have to do is I have to fetch the categories in This Server component so this is a server component meaning it has access to the database and we just recently created a util called Prisma DB which can be used to access the database so let's use it here I'm gonna go ahead and I'm gonna write const categories to be await Prisma DB from Prisma DB dot category dot find many like this foreign we have an error because our weight is not available unless we turn this function into an asynchronous arrow function like this great so if you're getting any errors or if you did not get this autocomplete in category or your category is underlined make sure that in your schema.prisma so inside of this folder you named it category and make sure you did npx Prisma DB push and npx Prisma generate but if you successfully seeded your database in here then you're not going to have any problems and this is definitely going to work for you perfect now that we have the categories just below the search input we're going to create a new component called categories like this and we're gonna pass in data to be categories like this and if you save of course we are going to get an error so let's go ahead inside of our components and create a new folder new file called categories.tsx like this let's go inside and let's mark this as use client like this let's create an interface categories props like that it's going to have data which is an array of category which you can import from at Prisma slash client and make sure you put an array at the end like this perfect now let's write export const categories like this and just return a div same categories like that now let's go back inside of our root page and just import these categories from add slash components categories so the same way we did uh with search input and just save so you don't have any more errors and you can see how this now says categories right here perfect now let's actually style this so it looks like something first let's enable the props so I'm gonna give it a data and the type is going to be categories props like this so data is now a type of category which has an ID and name so exactly the same in as in our schema Prisma perfect now let's go ahead and let's actually style this so I'm going to go ahead and in this main div I'm going to give it a class name of w Dash full overflow Dash x dash Auto space Dash X-2 Flex np-1 like this great and now instead of writing categories here I'm going to add a native button element and I'm gonna go ahead and I'm gonna give it a class name which is going to be CN from s slash lib slash utils and I'm going to separate this too because uh one is a global import and one is my local Import in here and let's go ahead and let's give this some class names so I'm using backticks here just because I want to order them all uh in different lines so if you want to you can use normal uh text like this but you have to write all of your classes in one line then but for you I want it to be more readable because we're going to have a lot of classes so let's write Flex items Dash Center text Dash Center text Dash extra small on a medium devices is going to be text SM PX is going to be 2 but the medium devices is going to be bx-4 py is going to be 2 but on medium is going to be py3 like this great rounded is going to be medium BG primary slash 10 hover it's going to change the opacity to be 75 like that and transition like this great and inside of this button I'm just going to write newest like this great so we have our first category and this is going to be our hard-coded category so you can obviously see that this is not in our database but we don't care this is going to be used as a button to reset all categories once we click on it perfect and we're going to make use of this Dynamic function later but for now we don't have anything to make it Dynamic yet so what I want to do now is I want to use this data to either it over all of our categories which are in the database So Below this button go ahead and write data dot map item like this and go ahead and immediately return this exact thing so you can copy this entire button and paste it inside and obviously uh we just need to add a key here so key is going to be item dot ID like this and instead of newest is going to write item dot name like this there we go so you can see that now we have newest philosophy animals movies and TV scientists famous people and games so we have all the categories which we need very very cool great uh so I'm just gonna collapse this like that and now let's actually create some functionality that when we click on it uh it actually goes to the URL so the same way we did with our search when we search something it goes in the URL now we have to do the same for the category great so let's go ahead and let's create uh and let's add some hooks first so I'm gonna go ahead and write const router is equal to use the router from next slash navigation like this and I'm gonna move this with the Global Imports let's write const search params to be used search params from next navigation let's get the category ID so const category ID is going to be equal to search params.get category ID like this and make sure you didn't misspell it great now let's write const on quick to be ID stringed undefined like that and now let's go ahead and write const query is going to be category ID which is mapped to the ID which we are passing in the prop of on click great and now let's go ahead and write const URL to be Qs which of course we have to import so let's go ahead and write import Qs from query Dash string like this now that we have that we're going to write Q as dot stringify URL go ahead and add a URL which is window.location.href and query like this and we're gonna we're gonna pass in the prop skip now true like this and the router.push URL like that perfect so now let's go ahead and for this hard-coded button which is newest it's actually going to reset the category so it's going to go on click Arrow function on click undefined like this perfect now let's go ahead and go inside of this matte buttons and we're going to pass in a different on click so on click here is going to be item dot ID like this perfect so let's go ahead and let's test now if I go inside of my app and click on Philosophy for example you can see that it Maps the category ID for philosophy if I click on movies we have a completely different ID and same thing for every single thing that we try to click but if we click on newest it's completely reset because that's exactly what we want what I want to do now is I want to show that once we actually click on movies and TV I want to add a little highlight to this button so the user knows that movies NTV is selected so let's go ahead and let's do that so after your initial class names right here I'm going to add a comma and I'm going to write item.id is identical to category ID in that case I'm going to write BG Dash primary 25 otherwise it's going to be VG Dash primary slash 10 like this great and you can go ahead and copy this thing and paste it in this button as well uh actually don't paste it because it's going to be a little bit different so again after this initial classes in this hard-coded button here so after you finish these classes add a comma and just paste in if there is no category ID so exclamation point category ID in that case we're going to use BG Dash primary slash 25 otherwise VG Dash primary 10 like this great so let's see how that looks now so you can see that movies and TV is highlighted if I go to musicians musicians is highlighted if I click on newest everything is reset and newest is selected perfect you successfully finished the categories and now we can go ahead and actually create our create form where we are going to load these categories for other users to select when they create their custom AI companion great great job so far so in order to create our create form we first have to create a model for our companion so I'm going to go ahead and I'm going to close everything and I'm going to go inside of my Prisma schema.prisma right here so let's go ahead and let's create a model companion right here let's go ahead and let's give it an ID which is a string a decorator ID and a default value of uuid like this now let's go ahead and pass in the user ID which created this companion which is going to be a string let's go ahead and pass the user name from the Creator as well which is a type of string a source so this is going to be an image which each companion is going to have which is also going to be a string let's go ahead and give it a name which is going to be a string and it's going to have a special decorator DB dot text this is going to enable much longer characters for this property and it will also make it searchable and you will see that in a second let's go ahead and let's write prescription which is a string let's go ahead and write instructions which is also a string and VB dot text because it's gonna have almost 200 characters minimum let's go ahead and add seed which is also a string and DB dot text as well because it's going to be a larger field now let's go ahead and let's add create and add which is going to be a date time default is going to be now like this and let's go ahead and let's add updated at which is going to be date time and it's going to have a special decorator at updated at like this now let's create a relation between the companion and the category so I'm gonna go ahead and write category to be a category like this which is a relation so I'm just going to expand my screen here and we're going to use the fields to be category ID which we are going to create in a second and the references is also going to be ID like this and let's go ahead and write category ID be a string like this and now we have to go back inside of our category model and we have to add companions to be an array of companion like this there we go and now let's go ahead and let's add add index category ID like this so we resolve that warning perfect and now we have to enable uh full text search for our name because we have this search input right here and it's going to query by companion name so let's go ahead and let's modify our Prisma so it uses uh specific values and properties to enable search on that so first thing we have to do is we have to go inside of our generator client and Below provider add preview features go ahead and open an array and in the first string you're going to write a full text search like this and in the second one you're going to write full text index like this so make sure you have these two preview features great and then in the model companion right here you're gonna go ahead and you're gonna add just below this at index you're gonna add add full text to be named so we are going to enable full text search on the name field here perfect so now that we have that let's go ahead let's go inside of our terminal I'm going to shut down the app I'm going to write MPX Prisma generate then I'm gonna write MPX Prisma DB push so very important every time you change your Prisma it's important that you run those two commands perfect so now everything is up to date and you can npm run Dev your project again perfect so now we are ready to build our create page so let's go inside of our app folder inside a root right here inside the routes and create a new folder called companion like this and inside of that folder create a new folder open square brackets companion ID like this perfect so now inside of that create a new file page.tsx like this great so now that we have that let's go ahead and let's write companion ID age like this and let's just return a div saying hello companion ID like that perfect and now if you actually go ahead and click on your create button you should no longer get a 404 instead you should get redirected uh to slash companion Slash new which matches this structure which we created and you should get the hello companion ID text so let me explain why does this URL work in this folder structure so the companion is mapped to slash companion but companion ID is a dynamic part of the URL in our case it's new but later when we want to edit a companion it's obviously going to be a proper ID in here so that's what we're going to use to load the companion we want to edit but if we pass in explicitly new then we're gonna create a form for creation of a new companion so let's go ahead and let's create the interface for This Server component so interface companion ID page props like this it's going to have params and inside of those params we're going to get companion ID which is a type of string so make sure that you didn't misspell companion ID here and make sure that it it matches this folder companion ID so capital I and then lowercase D make sure you don't make mistakes when doing that because otherwise it's always going to be undefined and you won't be able to load anything and it's going to cause cryptic errors so just confirm that you didn't misspell companion ID in the params and in the folder name right here great so now I'm gonna go ahead I'm gonna mark this component as a synchronous one like this let's go ahead and extract params like that and let's assign companion ID page props like this perfect I'm gonna go ahead and I'm gonna write to do check subscription so in the future when we add our stripe subscription we are going to redirect user away from this page if they don't have an active subscription but we don't have that yet so we're just going to leave a to-do comment here and let's continue with our work so we're gonna attempt to fetch a companion using the param ID so cons companion is equal to 08 Prisma DB and make sure you import Prisma DB from Prisma DB dot companion again if you didn't get this autocomplete or if you get an underlying error make sure that this companion is the same name as in the schema Prisma model companion and make sure you run npx Prisma generate and npx Prisma DB push if you did all of those things and everything seems correct and you're still getting an error just go ahead and shut down your application and do command shift p and reload your window and the error should go away great so await Prisma DB dot companion dot find unique open an object and write where ID is equal to params dot companion ID like this perfect and basically what we are doing here we are attempting to fetch an existing companion using the ID from our URL but if we click on the sidebar then our ID is always going to be new meaning that this companion is not going to exist and we are going to use that information in order to determine whether we should show an edit page or a create page perfect and now that we have the companion or well don't have the companion let's fetch the categories const categories are going to be await Prisma DB dot category dot find many like this perfect and now that we have both of those we can remove this and we can write companion form which is a self-closing tab which does not exist yet and let's go ahead and let's pass in initial data to be companion like this and categories to be categories like this and if you save of course there's an error because companion form does not exist right now so what we have to do is we have to go inside of our companion ID and create a new folder called components and create a new file companion Dash form dot DSX like this and let's go ahead and write export const companion form like this and just return a div same companion form like this perfect go back inside of your page and just import companion form from dot slash components companion form like this and the error should go away and you should see the text companion form which is inside of this companion form right here so let's go ahead and let's mark this as use client like this let's go ahead now and let's create an interface so interface companion form props it's going to accept initial data which is a type of uh companion from Prisma client like this or it is no because there is a chance it's not found if the ID is new or something else which is completely invalid and we're also going to have categories which is going to be an array of a category model from Prisma client like this perfect so now we can go ahead and we can extract categories and initial data and assign the props of this component to companion phone props like this perfect so now we no longer have errors which we are passing here perfect it's exactly what we expect uh great so what we have to do now is we have to add our form components from chat CN UI so let's go ahead let's go inside of schatzian UI let's go ahead and find the form inside of this sidebar right here so I'm in the components go ahead and find the form right here and let's see what we have to run in order to get this so npx chat scene UI at latest add form I'm gonna copy this command and I'm gonna go ahead inside of my terminal here and I'm going to write npx chat cnui at latest add form like this so just go ahead and confirm this right here and while we are in this terminal let's also go ahead and let's add the text area so I'm going to go ahead and find the text area here and we're going to do the very same thing so we're going to find the appropriate command which is MPX chat cnui latest add text area so let's go ahead and run that so MPX chat cnui at latest add text area like this go ahead and confirm that as well let's go ahead and add the separator which is also something we are going to need so find a separator here and again we did the same thing let's find the command let's paste it here I'm just going to expand a bit so npx chat cnui at latest add separator like this confirm the installation we already have the uh the input so no need to install that and I think for now uh we are good to go actually no one more component we need is the select component so just above the separator is the select component so same thing here let's just go ahead and add this as well perfect so I'm copied this CLI command and there we go npx chat cnui at latest add select so just confirm that as well there we go so we added a couple of components from UI here so now we can go ahead and npm run Dev again perfect I'm going to expand this I'm gonna refresh my localhost inside great now let's go ahead and let's import a Zod so I'm gonna go here and I'm going to import all so Asterix as Z from Zod and we installed Zod when we run uh MPX chat cnui at latest add form so when we added the form it also added all of the packages we need in order to create uh beautiful forms so let's go ahead and let's create our form schema so const phone schema is going to be Z dot object it's going to have a name which is z dot string and a minimum is one so minimum character for a name is going to be one and we're gonna add a custom message name is required like this perfect so let's go ahead and let's copy this so the second property is going to be description like this and it's going to be description is required so you can go ahead and copy that again this time it's going to be for instructions but it's going to be this string but the minimum uh value inside is going to be 200 so we require at least 200 characters and we're gonna write um instructions require at least 200 characters like this perfect so go ahead and duplicate that again and this one is going to be seed same thing but instead of instructions we're going to write seed like this perfect and other than that we're also gonna have a source which is going to be minimum one so make sure you change the source to minimum one and the message here is going to be image is required like this and last one is going to be category ID and we're just going to write category is required like this great so we have our form schema now and what we can do now is we can actually create our Form controller so let's go ahead and write const form is equal use form from react Dash hook Dash form so make sure you import that so use form from react Dash hook Dash form like that great now that we have that let's give it a type using this Zod form schema which we just created so Z dot infer whoops Z dot infer go ahead and open pointing brackets again and write type up form schema like this great and go ahead and open parenthesis and an object here and let's add resolver to be Zod resolver and pass inform schema and we have to import this Zod resolver so let's go ahead and write import Zod resolver from at hook form slash resolvers slash Zod like this great so now we have the Zod resolver and now let's go ahead and let's add our default values so default values are either going to be the initial data which we have as a prop here or it's going to be an empty object with an empty name empty description empty instructions empty seed empty source and category ID it's not going to be empty but undefined because it's a select component perfect so we have that now now let's go ahead and let's extract the loading State using this Form controller right here because we're going to need it so I'm gonna go ahead and write const is loading to be formed.form state DOT is submitting like this whoops it's submitting like this and we're going to use that across our project now let's write our on submit function here so const on submit it's going to be in a synchronous function which has values which are a type of Z dot infer open pointy brackets and just write a type of form schema like that open an arrow function like this and for now I'm just going to write console log values like this so we can see what we are logging perfect so now that we have that let's go ahead and let's modify the return function here so I'm going to add a class name to this div named H dash full b-4 space Dash Y dash 2 max Dash w-3 Excel and mx-auto like this perfect and now inside I'm gonna go ahead and I'm going to import form from add slash components slash UI form like this so make sure you've imported form from add slash components UI form and I'm gonna start separating my imports here great and this form requires all the props that our Form controller has so in order to pass in all the props we can do a simple trick open square brackets sorry curly brackets and just spread the form like that so our form is this constant which holds the use form from react hook form and it has a bunch of useful stuff inside and all of that is needed for this form component great now inside we're going to use a native HTML element form so lowercase form and we're gonna go ahead and pass on submit to be form dot handle submit on submit in parenthesis so we are using the on submit from this hook use form and we are passing it our custom on submit so that's why our on submit has these values which have a certain type of form schema great so now that we have that let's also go ahead and give this form a class name so I'm going to write class name to be space Dash y-8 and padding bottom 10 like this perfect let's go ahead and let's create a div here with a class name space Dash y-2 W Dash full and call Dash span Dash 2 like this crit now inside let's go ahead and create an empty div and let's create an H3 element inside with the text general information like this and let's give this H3 a class name of text LG and font Dash medium like that great now below this H3 element we're going to add a paragraph which is going to say general information about your companion like this and give it a class name of text SM and text that needed Dash foreground like this there we go and outside of this empty div right here uh sorry outside of this div without class names which is holding our H3 element and our paragraph we're gonna go ahead and add a separator component and make sure you don't import this from Radix but import it from add slash components UI separator so separator from s slash components UI separator make sure you didn't accidentally import it from Radix great and now let's add this separator a class name to be vg- primary slash 10 like this there we go see now you can see how we have general information Title Here some description and a nice little separator inside great so what we have to do now is we have to create our first well our first uh uh a field so in order to do that go outside of this div but still inside the form and add a form field from add slash components UI form and it is a self-closing tag so I've imported form field from add slash components UI form great let's go ahead and let's give it a name of a source like this and a render function like this extract the field inside of this and go ahead and return a form item again from add slash components UI form so I've imported from field and form item like this and let's go ahead and give this form item a class name like this of Plex Flex Dash call items Dash Center justify Dash Center space Dash y-4 and call Dash span-2 like this perfect and inside let's add form control again from add slash components UI form so make sure you've imported form control like this and inside of that we're actually just gonna write for now image upload component we don't have it yet great and just below the form a control you're going to add form message from add slash components UI form so make sure you import that as well great perfect so right now nothing is visible because we don't have a proper component inside so that's what we have to do next so before we continue I just noticed that I added this call spam here uh we are actually not using grid so you can go ahead and remove whole span class from this form item and also from this div encapsulating our general information and general information about your companion text we are not gonna need that great so in order to create our image upload component we need to set up our cloudinary account so either visit cloudinary.com or go to cloudinary Google and just click on login right here or create your account so first thing we're gonna do is we're gonna get our Cloud name so you can visit that in your dashboard right here find the cloud name it's this short text and either copy it by clicking here or here and let's go ahead and let's add that in our DOT environment file so go right here after the database URL add next underscore public underscore cloudinary underscore Cloud underscore name and just paste that code right here so next public cloudinary Cloud name like this perfect what we have to do next is we have to install a package called next cloudinary so go inside of your terminal right here and write npm install next Dash cloudinery like this so wait a couple of seconds for this to install and now we can continue developing so I'm gonna go ahead and I'm gonna go uh first let's actually run the application yes I forgot that so npm run Dev let's run our application keep the cloudinery open you're gonna need it and refresh your localhost here and let's go back inside of our app folder root routes companion companion ID components companion form right here and let's go ahead and let's create this image upload component so I'm gonna go ahead I'm gonna go inside of my Global components right here and I'm gonna create a new file called image Dash upload.tsx like this so let's go ahead and let's mark this as use client let's go ahead and let's create an interface image upload props like this it's going to take a value which is a type of string it's going to have an on change which is a function which accepts a source which is a string in these parameters and returns an empty void and we're going to have a disabled crop which is an optional Boolean like this now let's go ahead and let's write export const image upload like this which is going to have a lot value on change and disabled like this and let's just map these props to image upload props interface right here great so let's go ahead and return this Arrow function and first let's go ahead and let's resolve uh hydration errors with this component so this component uses cloudinery and there is a chance that it causes hydration errors so in order to resolve this there is a simple trick that we can use by enabling a set State and watching whether that state has been set to true and that way we know when we can safely render this component so I'm going to explain that one more time while I develop this so go ahead and write const is mounted set is mounted to be equal to use state from react and default value is false so we are going to change this in a simple use effect by default it's false and now we're going to write use effect again so I imported use State and use effect from react go ahead and open this simple use effect add a dependency array and all I'm going to do inside is set is mounted to true so why am I doing this well very simple because I'm gonna do this check here if we are not mounted return now like this basically we're also going to have another return function here which is going to have all the cloudinary code of course and that cloud binary code can cause errors between server side rendering and client rendering so a simple trick we are doing is we are checking if we are mounted so this use effect which switches this is mounted to True is only going to run once we finish server side rendering and get to client-side rendering meaning that if I in this check I'm checking if we are not mounted so in server side rendering we are going to return null meaning that none of this code below this is going to be able to cause any hydration errors using this simple trick and then when we get to client side it's going to be set to true and this if Clause is not going to break our code we're just gonna return everything else so that is a very simple explanation of why I'm doing this great now let's go ahead and let's write return again so this is going to happen on client side let's go ahead and write a div right here with the class name space slash y-4 W Dash full Flex Flex Dash call like this justify Dash Center and item slash Center like this great now inside of that let's go ahead and let's add our CLD upload button component like this and we can import this CLD upload button component from next Dash cloudinery so let's go ahead and write import seal the Apple button from next Dash cloudinery like this great now what we have to give this CLD upload button are a couple of props so let's give it options and let's limit the amount of files which user will be able to upload so max files is going to be one like this great and then let's go ahead and give it an upload press it so where do we get this well that's why I told you to keep the cloudinary open you're gonna have to go inside of your cloudinery and all the way to the bottom you're gonna have to find the settings so click on the settings and then you're gonna have to go inside of your upload right here section so I already have a bunch of upload presets right here if this is your first time using this you're probably gonna have only ml default so what you have to do is you have to click this button add upload press it and very important thing you have to set signing mode to unsigned like this so this is the only thing you have to change and click save like this and then it's going to be at the top of your upload presets and you can just copy it like this I'll just click copy like that and that is going to be the upload present which you're going to paste in this prop right here there we go so that's all you have to do you no longer have to use cloud in every there we go great and now what I want to do inside of this CLD upload button is I want to create a new div with a class name we're going to add p-4 border Dash 4 border Dash dashed border Dash primary slash 10 rounded LG power opacity-75 like that transition blacks black stash call space Dash Y dash 2 items Dash Center and justify Dash Center like that now let's go ahead and add another div inside which is going to hold our placeholder image and later our actual uploaded image so inside I'm gonna write relative h-40 and w-40 great and inside we're gonna go ahead and add the image component so let's go ahead and let's add image which you can import from next slash image it is a self-closing tag and just make sure you've imported image from next slash image like this great let's go ahead and let's give it a fill property an off property saying uh upload like this and a source which for now is just going to be slash placeholder.svg like that and a class name of rounded Dash rounded LG and object dash cover like this so let's go ahead and add this placeholder.svg in order to do that we have to add it inside of our public folder right here so you can either find any image online or you can go inside of my repository link is in the description go inside of my public folder and go ahead and find placeholder.svg right here and you can just download this file like that and drag and drop it inside of my public folder like this and what I'm going to do is I'm just going to rename this to be placeholder like this so make sure it's not placeholder one or two it needs to be placeholder.svg like that perfect now let's go back here you can close this so no need to open all of this stuff just make sure you have placeholder.svg inside of your public folder great now let's actually add this component before we start doing any more functionality on off it so you can see how it actually looks so let's go back inside of companion Dash form where we have the text image upload component and let's actually replace this with image upload from add slash components slash image upload like this so I imported that from s slash components image upload great and now let's go ahead and let's give it a disabled off is loading which is this constant which we created here which is constantly reading from the is submitting State like that and I'm just gonna collapse this attributes like this besides disabled let's also give it an on change which is going to be field dot on change like this so we have the field from this this structured render Arrow function in the form field component and let's give it the value of field.value like this great let's go ahead and save this and look how cool this looks perfect so before we continue with this let's go ahead and let's actually create our our own change uh sorry our own click button our own click property to this CLD upload button inside image upload component so above options go ahead and add on upload right here it's going to be an arrow function which for the first prop gets the result which is a type of any and let's go ahead and call the on change prop and passing result dot info dot secure underscore URL like this there we go and now let's go ahead and let's test if this works so I'm going to go ahead I'm gonna click here you can see that I have a cloudinary opened I'm going to click browse and I'm going to choose Ronaldo right here so let's see I'm gonna refresh let's see maybe something uh went wrong yes I think something went wrong I'm gonna go ahead and debug and see what's going on oh well we forgot to add one thing so go back inside of your uh image upload right here and we hard coded the placeholder so what we have to do is we have to add uh a dynamic option here so either value or placeholder like this so let's try this again so I'm gonna upload this Ronaldo picture that I have and after I uploaded it I get an error and this error is because we are using an image inside the next image component but we did not configure the URL for it so let's go ahead and let's do that so you can see which hostname we have to add in our config so I'm going to zoom in a bit so you can see this message so once you get this message you have to copy this hostname right here or you can see click here to see more info here great so copy this hostname it's rest.cloudinary.com and what you have to do is you have to go inside of your uh inside of your a next dot config.js expand this object write images write domains open an array and just paste in rest.claudinary.com like this and what I highly suggest you do is you restart your application after this so I'm gonna restart and npm run Dev again and then I'm going to refresh my application so let's try this one more time uh okay I'm zoomed in a bit so let me just zoom out okay and I'm gonna select Ronaldo again like this and there we go so Ronaldo is now loaded and if you're wondering where did I generate these cool images from I put a link in the description for hotpot.ai it's a really cool product and I I tried many AI image generators for this product but they are the only ones who got consistent results for me so make sure to visit them with the link in the description if you want to generate cool images like I did for this project great so now that we have our image component uh image upload component working let's go back inside of our form so inside the app folder let's go inside the root routes companion companion ID components companion form right here let's go ahead and let's add some more Fields here so just below this self-closing form field tag we're gonna go ahead and add a div and this div is going to have class name grid grid Dash calls dash one uh MD grid Dash course Dash 2 and gap-4 like this great now inside of that div create a form field component which we already have imported it's a self-closing tag give this a name name like this control form dot control like that and a render prop like this which is an arrow function let's immediately destructure a field from it and just return an arrow function like this and inside you're going to render a form item which we already have let's go ahead and give this form item a class name of call Dar span-2 MD called where span dash one like this so I'm going to explain this Grid in a second just so we can see something visible on the screen so it's easier to explain let's add form label which we have to import from add slash components UI form so I've imported this right here so I'm just gonna go and quickly collapse all of these items so you can see it better so make sure you have form form control form field item label and message uh great so inside of this form label let's go ahead and let's write name like this and below that let's add form control which we already have imported and let's import our input component from add slash UI add slash components UI input and it's a self-closing tag so I've imported input from add slash components eui input like this great let's go ahead and let's give this a prop disabled if we are loading let's give this a placeholder Elon Musk for example and let's go ahead and spread the field prop inside so basically what we are doing here is we are using spread field in order to pass all the necessary props to input those props are on change on blur uh and many more props which we need so instead of manually mapping those props like we needed for this custom component you can see how I used field.onchange and fill.value so instead of doing that I just safely passed all of the fill components because this input is from chat cnui so it's mapped to correctly respond to uh form components from the very same chat cnui Library great so let's explain this grid right now so I created a grid which on small devices like mobile will fill the entire screen so only one column is available right but on medium screens and larger it's gonna take uh two columns meaning that if I expand my screen you can see that it's only taking half of the available space so there is space for another field right here but if we are on small devices that just doesn't look good so I want to want it to fill the entire space so that's what I do here on medium I only make it used uh one half of what it has because on medium it has two spaces so I'm only using one half of it but on mobile devices I wanted to use the entire screen great so now that we have the name ready uh below form control let's also just not forget to add form message like this it's a self-closing tag and we already added that uh in this image upload so we have it imported right here perfect so now that we have this inside of this very same div which has a grid let's go ahead and copy this self-closing tag form field let's paste it like this so now we have two name controls but we're going to change this to be description like this and let's change the form label to the description as well let's change the placeholder to be CEO and founder of Tesla Dash SpaceX like this great and one more thing I I forgot to add here is form description for each of these elements so let's go back to our name and just above form a message let's add form description so we have to import form description from add slash components UI form and inside I'm just gonna write this is how your AI companion will be named like this so now we have a description explaining what this film does great and we're going to do the same thing uh for the description so one description right here and inside uh I'm gonna go ahead and pass short description for your AI companion like this great so now that we have that let's go ahead uh and expand so you can see how that now looks see on large devices it feels it makes much better use of space but on small devices it's one above another great that's exactly uh what I wanted so now let's go ahead and let's add the Third Field for this form so it's still going to be inside of this div which has a grid grid so let's go ahead and let's copy this entire thing one more time so now we have a second description here but this one is going to be a bit different so instead of controlling description it's going to control category ID and instead of being you're a regular form uh uh input it's actually going to be a select component so I'm just going to clear everything and write it from scratch just so you can see exactly how it should look like so let's go ahead and let's write form item like this no need for any classes let's add form label and let's write a category like this below that let's add a select component from add slash components UI select so make sure you don't accidentally import this uh from Radix so I imported select from add slash components UI select and make sure you do the same great let's go ahead and let's give this select component some props so I'm going to give it a disable prop of is loading on value change is going to be field.on change like this value is going to be field dot value and default value is going to be also field dot value like this great now inside of this select let's go ahead and let's add form control which we already have imported and inside of form control let's add select trigger which is something we have to again import from add slash components UI select again just make sure you don't accidentally input it from Radix because it's just not going to work and it's not gonna look good great so let's give this select trigger a class name BG Dash background like this and let's go ahead and write another component inside of the select trigger called select value so again from add slash components UI select and it is a self-closing tag so make sure you import the select select trigger and select value and I'm already going to start collapsing this so you can see it better there we go so now that we have the select value let's go ahead and let's give it a couple of props so it's going to have a default value of field dot value like this and it's going to have a placeholder select a category like this great now let's go ahead outside of our form control here and let's add select content which is also something we have to import from add slash components UI select so select content I imported it right here and inside of Select content we're going to map over our categories which we have as a prop right here so let's go ahead inside of the select content and write categories.map let's get the individual category like this let's go ahead and return select item again something we have to import from add slash components UI select and it is uh not a cell closing tag so select item imported from add slash components UI select so always make sure there is no Radix in your Imports because it shouldn't be great now let's go ahead and let's give this select item I key of category dot ID like that let's go ahead and give it a value of category dot ID as well and inside login to render category dot name like this there we go and now just outside of this select let's go ahead and write form description like this and right select a category or your AI like this great so let's expand this now I'm going to refresh and there we go you can see that now I can select from the categories which I have fed in my database so the reason this is working let's go ahead and let's repeat what we did so we have companion ID folder which has page.dsx this is a server component which is able to fetch companion and it's able to fetch categories so we use the categories and we passed it to a client component called companion form as well as the initial data this companion form then has the categories right here and we have added the interface for that so we know that it's a Prisma model category and it's an array great and then we use those categories from this props right here in order to map them in this name category ID form field which uses the select com select component from chat cnui inside of the select content right here so that's why we can select this scientists and movies basically exactly what we exceeded our database with perfect so now that we have that we just have a couple of more uh Fields left so I'm gonna go ahead and I'm gonna go outside of this div right here so this div which has this grid right so this a div beginning with grid grid calls one and the grid calls two and gap-4 which encapsulates our name our description and our category so we're gonna go outside of that div so still inside of native form and what we are going to do is we are going to create a new div which is going to encapsulate the rest of our form with class name space Dash y-2 and W full like this great now inside of that let's go ahead and create a new div let's add an H3 element with configuration like this so you can see from now it says configuration here at the bottom let's go ahead and let's give this H3 element a class name of tax Dash LG and phone Dash medium like this great and below this H3 element we're going to have a paragraph detailed instructions or AI Behavior and let's give it a class name of text SM and text Dash muted Dash foreground like this great and outside of this Dev holding the H3 and the paragraph let's also add this separator which we already have imported like this and let's go ahead and give it a class name of BG Dash primary 10 like this great so let's expand to see how this looks so this section is general information meaning we add our image our name description and category and this section is configuration for instructions for our AI Behavior so this is we are going to add the instructions and the seed chat great so let's go ahead and let's continue developing this so you can go ahead and actually copy description or name field for example so I'm going to copy this form field which is a self-closing tag so I'm going to copy it for name you can do it for description as well but don't do it for category ID because it's way different it uses select great so uh just uh outside of this div right here go ahead and paste this and change this form field do not control the name but to control instructions like this and this form label is going to write instructions like that instead of the input we are actually going to use text area so let's go ahead and write text area like this from add slash components UI text area so I imported this like that great so now that we have that let's see how this looks okay now let's just modify it a little bit so I'm gonna go ahead and give it a class name of BG Dash background I just have a title here whoops BG Dash background like this and resize Dash none like that perfect let's also give it rows seven like this so it's a bit larger great so you can see how now we have a text area right here and I want to change the placeholder from Elon Musk to an actual example of how we should have how our instructions should look so our instructions should be as detailed as possible if you want this AI model to behave the uh the way you imagined so what you can actually do is go inside of my GitHub again and you can go inside of app folder inside of root folder routes companion companion ID so the same place we are right now go inside of these components and find the companion form so this very component which we are working with right now and here you you're gonna find this two constants that I have preamble and the seed chat and you can just copy both of this right here so these are very long texts which I've prepared for you so your AI models can be as advanced as possible right and uh if you don't like these two I actually have something else as well so in my repository you have this folder called Companions and inside I prepared it for Albert Einstein Cristiano Ronaldo Elon Eminem So choose one you like for example Cristiano and I prepared the Preamble for you and the seed chat so you can either copy it from here or less for now let's do exactly what I'm telling you to do so we have the same results go inside of root folder routes companion companion ID components and companion form right here and just copy this Constance seed chat and Preamble right here so I'm gonna go ahead and expand this and I'm gonna add it all the way to the top above everything like this so you can see I have this very very big text great and we're going to use this as placeholder for our instructions and for our seed chat so I'm gonna go ahead and I'm gonna find my text area uh for instructions and I'm going to replace the placeholder from Elon Musk uh to Preamble like this great so now if I expand this screen you can see that my instructions are now much more clear and user has a better understanding of how my instructions should look like great now let's just change the description of this field so this is going to be describe in detail your companion your companions backstory and relevant details like this and we have this error because we're using a backtick so let's just replace this with uh and apos and a semicolon so you can just use this in order to get rid of that error and still get as you can see the same result great or you can just write companions backstory right it doesn't really matter great so now we have that and all that's left is one more field so copy this field right here paste it below so now we have two instructions and change this new one to be uh seed like this so that's the uh the field is going to control and the form label is going to be example conversation like this and instead of placeholder being Preamble is going to be seed underscore chat from these two constants which we just added great so now our user can clearly see what the example conversation is supposed to look like so we have the instructions and we also have the example conversation right here great this looks really really good now what I want to do is I want to go outside of this last form field which we just added which controls the example conversation but still inside of this native HTML form element and let's go ahead and let's add a div with the class name W Dash full Flex justify Dash Center like that let's go ahead and add a button so you can import that from add slash components UI button so I imported it right here great and let's go ahead and add size LG and let's go ahead and write disabled is loading like this and let's just write uh depending if we have initial data in that case it's going to be edit your companion so if we successfully loaded a companion from our server component and pass the initial data in that case we are obviously editing our companion otherwise we're going to write create your companion like this great and after that we're going to use an icon called Band 2 from Lucid react like this so make sure you've imported Band 2 from Lucid react and I'm just going to move this input all the way to the top with the rest of the Global Imports and let's give this a class name of W-4 h-4 and ml-2 like this great so let's see how this looks right now there we go I think this looks very very good and if we click here you can see how we have errors because images required name description category all of those things are required and it seems like our category is missing a form message so let's see if we can debug that very quickly let's find our category control uh so just a second okay uh oh where is it so we have the name the description and there we go category ID so find this find the form description and Below form description we forgot to add one message there we go so now you can see that we also have the required text below the category and now what I want to do is I want to test this on submit function where only thing I've added is a console log of values so I'm going to expand my screen right here as much as I can okay this looks good and I'm gonna go ahead and open my uh uh my inspect element on the side so we can see that console log let's go ahead and let's add an image again if you want these cool images visit the link in the description hotpot.ai great let's add it name Cristiano Ronaldo let's give it a description of famous footballer let's go ahead and select the category of famous people like this let's give it an instructions of this is a test instruction but we need 200 characters so I'm just going to copy this over and over again until the error goes away great so just copy it as many times as you can and you can also copy it and paste it in the conversation like this and click create your companion and there we go in the console log you should see uh all of the fields you've added so we have the category ID description instructions name seed and Source great great job you finished the UI for uh the creation form what we have to do next is obviously the API routes which are actually going to add this to our database amazing amazing job so far great now that we know that our values are correct let's go ahead and let's actually create an API call with these values so I'm going to close this I'm going to extract sorry expand my code screen and I'm going to go ahead and I'm gonna shut down my application and I'm gonna run npm install axios like this wait a couple of seconds and you can run the app again and just make sure you refresh your localhost 3000 so what I'm going to do now is I'm going to modify this on submit function uh using axis so we're going to go ahead and we're going to open a try and catch block here like this let's of course extract the error here and let's just log the error saying something went wrong like this we're going to change this console log to an actual toast later and inside of this a try what we want to do is we want to check whether we have uh initial data or not so we know whether we need to do an axis call to update using these values or if we need to create a new one so I'm going to go ahead and write if we have initial data in that case we are doing editing so await axis dot whoops we did not import axios so let's go ahead and let's just import axios at the top like this so a weight axios dot patch because if we have initial data we're going to have the companion ID so we can safely go ahead and add your backdicks and write slash API slash companion which is not something we had yet but we are going to create it so slash API slash companion like this slash go ahead and pass in initial data dot ID and pass in the values which we have in this props right here so this is going to be for our update companion functionality right this is not going to be for creation and then if we don't have initial data so in the else this is where we are going to create one so this is going to be create companion functionality like this and this is going to use axios.post so let's go ahead and write a wait axios.post and no need for backticks here because we're not using any existing values so we're just going to write slash API slash companion and we're going to pass in the values to create the companion like this um great and before we move on I just want to add the toaster here so let's go ahead and let's do that in order to do that we have to go back inside chat cnui and you have to find your toast right here once you find the toast uh here we have the command so npx chassis and UI latest add toast let's go ahead inside of our terminal and let's run npx chat cnue at latest add toast like this and this is actually going to add a couple of stuff not just usually uh as it does just one components it's actually going to add this toaster which we have to add as well as some hooks so let's go ahead and run the app again and let's go ahead and follow the Step 2 of adding the toaster component and that is that we have to go inside of our app folder inside of layout.dsx inside of here so the same place where we added the theme provider so let's go ahead and let's go inside of our body inside of the theme provider and just below the children go ahead and add a toaster and you can import that from add slash components UI toaster which is the what we just added by running this command npx chat CN latest add toast great so make sure you import toaster from add slash components UI toaster like that and just save the application great and now let's go back in the companion form which is located right here so root routes companion companion ID components companion form and what you're going to do is you're going to use this hook use toast to get access to the toast so let's go ahead and write const the structure toast from use toast from add slash components UI use toast like this so make sure you've imported that use those from components UI use toast and now we have that dose right here and we can replace this error with an actual toast so let's go ahead and open the toast give it the variant of destructive like this and give it a description of something went wrong like this perfect so now that we have that let's go ahead and let's also add one if it's been successful so here after this awaits have been done let's go ahead and call the toast again uh the variant can stay the same and description is just going to be success like this perfect and let's also get our router so I'm gonna go here where we have the toast and I'm gonna call the router const Router is equal use a router from next slash navigation like this so make sure you've imported use router from next slash navigation and now that we have the router we can go ahead and in this a try block here so after everything's been successful either disco or disco has succeeded we've shown the success toast let's go ahead and let's run router.refresh so what rather that refresh is going to do is it's going to refresh all server components so all server components are going to refresh the data from the database ensuring that it's going to load this newest companion which we just created or which we just edited so it's going to be immediately reflected and after we refresh let's do router.push to the home page like this great what we have to do now is we have to create the slash API companion route so let's go ahead and let's do that now so I'm just going to expand all of this here I'm going to close everything for now I'm going inside of my app folder and I'm going to go and create a new folder called API like this and inside of that I'm going to go ahead and create a new folder again called companion like this and inside of that create a new file route.ds so this is going to be slash API companion our post route so let's go ahead and write export asynchronous function post like this which is has a rack which is a type of request like this and let's go ahead and let's open a try and catch block here so let's immediately resolve the error inside and all I want to do in the error is the console log and let's just write companion post so we can distinguish where the error is coming from and just this and let's return new next response which we can import from next slash server internal error like this and pass in a status of 500 like that great now let's go inside of our try block and let's see what we have to do here so first let's get our body using request sorry using await request dot Json like this great now let's go ahead and let's fetch our current user so const user is equal await current user from clerk slash next JS like this great now let's go ahead and let's extract all the things we need from the body which is Source name description instructions seed and category ID from body like this so Source name description uh I think I misspelled this so instructions like this seed and category ID so make sure you don't miss palette so you need to have all the fields which you have in your component companion form so those are name description instructions seed source and category ID so make sure you have all of those and make sure you don't misspell them so they need to match what you send great now that you have that let's go ahead and let's check if we are logged in so we're gonna know that whether we are actually whether we were able to load this user or not so let's go ahead and do if there is no user or if there is no user ID or if there is no user.first name like this in that case return new next response unauthorized and a status of 401 like this great now what I want to do is I want to check if any of these fields is missing so if there is no Source or if there is no name or if there is no description or if there is no instructions or if there is no seed or if there is no category ID in that case return new next response missing required fields and let's give it a status of 400 like this great so now that we have that I'm gonna go ahead and I'm gonna add a comment here to do check for subscription so right now we don't have subscription but later we're gonna have it and we're gonna prevent the users from using this API route if they are not currently subscribed on a valid Pro subscription but we're gonna leave that for later first we're gonna finish the entire Project without stripe and only then are we gonna add this right great so let's go ahead and let's write cons companion is equal to 08 Prisma DB which you can import from s slash lib slash Prisma DB and I'm just gonna go ahead and separate these inputs like this so await Prisma DB dot companion dot create like this and I'm gonna pass in the data category ID user ID which is user.id username which is user.name sorry user.firstname like this source name of the companion description of the companion instructions for the companion and see the chat for the companion there we go and now let's go ahead and let's just um return new sorry return nextresponse dot Json companion like this there we go so this route is now done and now we can go back in our companion form and we can actually test this out so I'm going to expand everything again uh and I'm gonna go ahead and fill all this information all right so I just quickly filled this information with the Ronaldo picture Cristiano and all the name description category and I just put some dummy text right here and I'm gonna go ahead and I'm going to open my inspect element and I'm going to go ahead and I'm gonna watch the network tab because that's the one I'm interested in right now so I filled all the information and now if my code is correct I should be going uh to slash API slash companion and paste my values so let's go ahead and see if we did that correctly so I'm going to click create your companion and it looks like we are sending the correct payload so we send the category ID we sent the description okay and it looks like this uh works correctly right here so you can see the response it created a new companion with an ID with the user ID username Source name description uh everything basically that we need but it is not a redirect so I just want to test why that happened could be because I didn't refresh my page perhaps uh I'm not entirely sure so I'm just gonna try one more time to see if it's going to refresh the page this time so I'm gonna go ahead uh I'm gonna add another picture like this let's just wait for it uh to upload okay Cristiano Ronaldo 2 games footballer too well okay we're just doing random things right now it doesn't really matter we're gonna remove this and replace them with actual characters later so I let's try again create your companion there we go I got a success message and I'm redirected to the home page perfect and now in order to check if these are actually in your database let's go ahead and let's use the Prisma studio so I'm gonna go ahead and I'm gonna open a new terminal and I'm going to write npx Prisma Studio like this meaning that I have my studio running on localhost 55555 great so I'm going to zoom this in a bit and I'm gonna click on this plus icon or you can just maybe you have this view so basically you should have the companion now and you can see that I have the two companions uh both created by Antonio this is my user ID and both of them are Cristiano Ronaldo famous footballer you can see basically exactly what we just created and you can see the relation uh with the category that we have movies and TV for this one and this one is famous people great so our form officially works now if you want to you can already try the edit form by copying the ID and going to slash companion Slash new but instead of new pasting the ID of the companion and there we go you can see how now I can edit my companion so let's go ahead and write Cristiano Ronaldo three for example so you don't have to do this we're going to do this later separately with the proper edit button but if you quickly want to do it you can go ahead and just copy the ID copy the ID from Prisma studio if you have access to it and replace your url to be slash companion but instead of new which is to create new one replace that new with the actual ID of the companion you're trying to edit and you should see this screen filled with your companion uh information and if I change this to Cristiano Ronaldo uh three for example and click edit your companion it's not going to work because as we have not created a route to update our companion yet so let's go ahead and let's quickly do that in order to fix that so we have to create this slash API companion with the ID so the reason I want to do this already is because it's very simple to do and we are already familiar with this API route which we use to uh create the companion and it's going to be very similar if not almost exactly the same uh for the uh patch route so let's go ahead and let's do that now so inside of your API companion folder create a new folder companion ID like this in square brackets like that and inside create a new file route.ds like this and what you can do is copy the entire post route for the for creating the companion and you can just paste that in the patch route right here and now of course we're just going to modify this a little bit so besides just having well first things first it's not going to be a post request it's going to be a patch request and besides just having the request in its uh parameters here it's also going to have params like this so go ahead and destructure the parents like that and let's give it a type parents is an object which has companion ID which is a type of string so make sure that this companion ID matches the folder name which you uh wrote right here so it needs to be in square brackets and the casing is important so capital I in ID is important so otherwise this is always going to be undefined for you great so make sure you do that and now let's go ahead and before we check anything let's check if we have that params.conpanion ID if we don't have it return new next response companion ID is required like that with the status of 400 like that great and all of this can stay exactly the same same thing for this check for subscription we're gonna do that later but instead of calling dot create we're going to call Dot update which means we have to add a where object here and where is going to be very simple it's going to be ID ramps foreign companion ID like this great and let's just quickly fix this error so instead of companion post is going to be companion patch like this perfect so now uh this should actually work because the route that we have now is a slash API slash companion slash the actual ID of the companion and if you look in our companion form for the patch we have slash API slash Companions and the actual ID of our companion so I'm gonna go ahead and test now so I'm going to refresh uh so the the way I uh loaded this edit page is I went into my Prisma Studio I looked at my companions I copied the ID of one of them I went back in my uh in my form right here and I replaced the Slash new with the actual ID and there we go now I can edit this Cristiano Ronaldo and I'm going to change this to Cristiano Ronaldo three and I'm gonna attempt to save let's see if that works seems like it does perfect and let me just so refresh this Prisma Studio to see if the data has been updated and it did you can see Cristiano uh Ronaldo three perfect so now we have uh both our creation and uh updating our companions uh functional great great job so far what we're gonna do next is we're actually gonna go ahead and map over them uh in uh in this home page right here so you can actually start seeing some chat functionality with those characters amazing job so far let's go ahead and let's find a way to render our companions right here so before we continue doing this make sure that in your Prisma Studio you have at least one companion so you can render something here if you have nothing in your Prisma studio in the companions field make sure that you create one using our new form right here and then go back to the home page right here so the first thing we have to do is we have to add another model inside of our schema Prisma and connect it with the companion model so let's go inside of the Prisma folder schema.prisma right here and let's go ahead and let's create an enum for the role of our new model which is going to be called message so let's go ahead and write annum role like this and we're going to have two possibilities either user or system like this so it's going to make sense in a second when we create a model message like this so model message is going to have an ID which is going to have which is going to be a type of string ID decorator and default is going to be uuid like this and then it's going to have a roll which is going to be a type of this role which we just created here so basically the message can either be sent from the user side or from the system site so that's why I wanted to create that and now let's go ahead and let's give this message some content which is going to be a type of string and DB dot text I like that and let's go ahead and give it created at which is a date time with a default value of now like this and let's go ahead and give it updated ad which is also date time but it uses a special decorator updated ad like this great and now let's go ahead and let's give it a companion ID which is a type of string and a user ID which is also a type of string like this and now let's create a relation with the companion using companion companion model add relation like this Fields is going to be companion ID references is going to be ID as well I'm just going to expand my screen so you can see this better like this so references is going to be ID and we're also going to have on delete Cascade like this so we can safely remove uh uh we can safely remove the companion and all the messages are going to be deleted as well for that companion great so that's why this on the Cascade is important otherwise if you don't add this on the lit Cascade and you try to delete your companion you're gonna get an error because there are outstanding relations with the message model so that's why we here give it an instruction that when the companion has been deleted go ahead and Cascade the message model great and now that we have that we have to create an equivalent relation in the companion model so go ahead inside of the your companion model here and just below this category ID go ahead and add messages like this and it's going to be a message model which is an array like this great and now we no longer have an error but we do have a warning and we can fix that quickly by adding at index like this and inside of that array add companion ID like this there we go so now the message model is ready and what do we have to do every time we modify our Prisma well I suggest you shut down your application and then run the two important commands npx Prisma generate so we add it to node modules and then ntx Prisma DB push so we push it to our mySQL database on planet scale like that and after this is done you can go ahead and run your application on the localhost 3000 again and make sure you refresh your application so it's everything is up to date great now that that is ready uh where we have to go is we have to go inside of our ad folder a root right here inside of routes and go ahead and go inside of page.tsx right here so this is our root page and the first thing I want to extract from here is our search parents so you might be wondering well what are the search parents where those are going to be this stuff in our URL which are triggered when we click on a category or when we search for something for example so in my URL now I have category ID and I have name so I want to extract that in this server component so I can fetch my companions using those filters if there are any so for now I'm gonna clear everything from my URL like this I'm going to expand my code screen and let's go ahead and let's write interface root page props like this we're gonna have search params like this category ID which is a type of string and the name which is a type of string so we know where we get category ID and where we get name but where do we get search params why is it named like this well that is a convention in next 13 server components so if you're wondering where did these search programs come from why do we have it here every server component has them so that's how you can access that in every single server component very cool now let's go ahead inside of this Arrow function and let's go ahead and extract our search params like this and now we have to assign the props root page props like this great and now above these categories let's go ahead and let's load all the data needed for our companions so cons data is equal await Prisma DB dot companion like this dot find many let's go ahead and use the where to filter out which companions we want to load so first we're going to check for the category ID like this search params dot category ID so if there is any category ID selected we're only going to load those companions which fit that category ID great and then let's go ahead and use the full text search on the name field so I want to quickly go back to my schema Prisma and go inside of the companion model so for the name what we did is we added the string type and we added DB text so DB text along other stuff that it does also makes the field searchable using this full text search and full text index preview feature right here so that and we also enabled full text on the name uh uh field right here so that's why we can do this we can do name we can open an object and write search like this search params dot name like this so we can use that to do a full text search on the name perfect now that we have that we can we're done with our aware objects and now let's go ahead and let's add an order by object and I wanna order by the newest created so created at is going to be descending and last thing I want to do is I want to add a another query in this uh Prisma DB companion find many which is going to count all the messages which this companion has so regardless of which user is logged in I want to load every single message that has been generated for this companion so we want to show users how many people talk to this companion how many messages has this generated so far great so let's go ahead and write include like this let's go ahead and write underscore count like that open another object and right select like this and messages true like this so we could have also done this a different way of course we could have done a separate const messages count await Prisma DB dot messages and then do a very complex query depending on all the data but I think there's no need for that this is a much better way of doing it so in this very same query where we load our companions we also include a underscore count where we select all the message relations and we created a relation right here so we have the messages so it's going to count how many items are in this array of messages and provided that in our account field underscore count field great so now that we have that save this file and Below categories we're going to create a new component called companions like this and give it the data of data and it is a self-closing tag now of course we're going to get an error because companions does not exist so let's go inside of our components folder and create a new file companions.dsx like this let's go ahead and write export const export cons companions like this and return a div companions like that and now let's go back inside of our root page where we have the Companions and let's just import the companions from ad slash components companions like this so now we have the companions so we can save the file and we are no longer getting an error but we have a text rendering companions here but we do have a type error so let's go back inside of our companions component and let's go ahead and create an interface companion props like this so data is going to be go ahead and open parenthesis like this it's going to be a companion like this and we included something called underscore count messages number like this and now just go ahead and add a an ad uh sorry add the array at the end and let me just import this companion so this companion comes from ad Prisma slash client like this and let me just see why we're getting these errors here all right so I did a stupid mistake I'm missing this right here so make sure you have underscore count then this not the semicolon but the other thing I forgot what it's called let's call it double dot okay so I forgot to put the double dot after count so just make sure you put that and then there are no uh errors here great so basically we created an array of a companion model from Prisma but we uh we told the typescript it also has something that you might not expect which is the underscore count with the messages counting here because that's what we included right here great so now that we have that let's go ahead and let's extract this data here and let's add companion props like this great now let's go ahead and let's uh create our empty state so we're gonna write um if beta.length is zero so if we loaded zero companions that can either happen if we didn't add any companions at all or if we entered a search which does not match anything or if we chose a category which has no companions so if that happens let's go ahead and let's return a div let's give this div a class name of pt-10 flex Flex Dash column like this items Dash Center justify Dash Center and space Dash y space Dash Y-3 like this and inside of this div we're going to create another div which is going to hold our empty placeholder image so this div is going to be a relative w-60 and h-60 as well and inside we're going to add the image component from next slash image like this so make sure you've imported image from next slash image let's go ahead and let's give this a Field property and let's give it class name of grayscale like this so it looks black and white and let's give it an out of empty and let's give it a source of Slash empty.png so we don't have this Source right here so let's go ahead and let's add it to our public folder so prepare your public folder here where you already added placeholder.svg go inside of my repository go into public and find empty dot PNG like this so just copy this image download it and paste it in your public folder and make sure it's named mt.png like this or you can of course find any image you like it doesn't matter great so now we have that working and let's see if we can use some query to trigger that so I'm going to type something completely random okay and there we go so we have the image showing right here but I also want to add a text below that image so let's go ahead uh to this div which is encapsulating our image go below it and write a paragraph no companions found like this and let's just give it a class name of text SM and text muted Dash foreground like this so it looks a bit better great now go ahead and remove this search so this should not appear now but you should see this companion's text and let's actually create an iteration over our companions so in order to begin doing that we have to add another shot CN component this time it's going to be the card component so go ahead and find the card component in chat cnui and this is the command we have to run and the actual latest add card I'm gonna copy that here I'm going inside of my terminal I'm going to expand this great and this is what I'm going to write so npx chat CN Dash UI at latest add card go ahead and confirm this installation and you can run your project again and of course if you actually shut down your project make sure you refresh it after you you've run it again great now let's go ahead and let's give this div a grid so class name grid grid Dash calls Dash 2 so that's going to be on small devices on medium sorry on small devices that sorry grid calls 2 is an extra small devices now on small devices we're going to have grid holes three on medium devices we're going to have grid calls 4 on large devices we're going to do grid Dash course Dash four again my apologies Dash five and then Excel we're going to have grid Dash codes dash six like that and let's give this a grid a gap of two and padding bottom of 10 like this great so now inside of this instead of that text companion we're actually going to go ahead and write data.map get the individual item and we're gonna go ahead and immediately render our card from dot slash UI card so that is what we just added so let me show you where I imported that so right here import card from dot slash UI card or slash components UI card like this great so now that we have that let's give this card a key well I'm actually going to collapse this so let's give this card a key of item.id because it's a companion so it has an ID and let's give it the class name of BG Dash primary that slash 10 rounded Dash Excel cursor Dash pointer hover Dash opacity Dash 75 and transition and Border Dash 0 like this great now inside of that card we're going to add a link component from next slash link so make sure you've imported a link from Mac slash link and I'm gonna keep it at the top great let's go ahead and let's give this link an href go ahead and open curly brackets and add some backticks inside and we're gonna write slash chat slash item dot ID so this route does not exist yet but it's going to exist in the future now let's add card header from add slash components UI card so make sure you've imported the card header like this inside of that we're going to go ahead and give it class name Flex items Dash Center justify Dash Center text Dash Center and text Dash muted Dash foreground like this and inside let's go ahead and add a div which is going to encapsulate our image so relative w-32 and h-32 like this and let's go ahead and render the image which we already have imported because we used it so the image comes from next slash image like this okay and we're gonna go ahead and give it a source of item.source a fill property and a class name rounded Dash Excel object dash cover like this and an ALT is going to be character or companion whatever you like great so you can see how I added two Ronaldo's and now I have to run all those rendering right here and if I expand you can see that they feel different types of screen great so let's continue developing this and just outside of this div which is encapsulating our image let's go ahead and let's add a paragraph which is going to render our companion name like this and let's give this a class name of font Dash bold like that great below that div we're going to go ahead and add another paragraph with the class name text Dash extra small like this and we're going to render item dot description like the shs Cristiano Ronaldo famous footballer too and same thing here great so we can now end the card header and we can actually open the card footer from add slash components UI card so make sure you've imported that as well so I have card card footer and card header from add slash components uh UI card and in this card further go ahead and give it a class name of flex items Dash Center justify Dash between tax Dash extra small and tax muted Dash uh foreground like this okay now inside of this card footer go ahead and create a p which is going to render item dot username like this and you can add a little add sign so it looks like let's say it indicates that this is from that user and let's give this a class name of lowercase like this I just think it looks better great and now we're going to create a div below that and the class name of flex items Dash Center like this first element is going to be our messages Square icon from Lucid react so make sure you've imported messages Square from Lucid react like this I'm going to move it to the top with the others uh and we're gonna go ahead and we're gonna give this a class name of w-3 h-3 and margin right one like this so very small icon and below that we're going to render item dot underscore count dot messages which are both going to be zero for now but when we start adding some messages it's automatically going to get updated inside great great job so you've finished uh creating a grid for these items and if you click on one of those you're gonna get to a 404 so what we have to do next is we have to create our chat screen great job so far let's go ahead and let's create this 404 page right here so in order to do that what we have to do is we have to create a new route group inside of this app folder right here because I don't want it to have the same layout with the sidebar and navigation bar I want it to seem like a separate layout with a separate um well with a separate design so let's go ahead inside of the app folder and create a new folder in parenthesis chat like this so this will not affect the URL yet but inside we're going to go ahead and create a new folder called routes like this and inside of that we're going to create another folder and this one is not going to be in parenthesis so this is going to be an actual folder which affects the URL and it's going to be called chat and inside of that a new folder in square brackets chat ID like this and this is where we are going to create our page.tsx like this so let's go ahead and let's create chat ID page and let's create a div hello chat ID page like this so now if you actually go ahead and try and click on one of your companions you should no longer get a 404 but you should get an empty hello chat ID page so the reason that is working is because pay attention to my URL say slash chat and then the ID like this and that is where we we added that inside of our routes companion uh sorry inside of our routes page.tsx right here inside of this companions folder so when we click on one of the companions we use a link href to redirect the slash chat slash ID of the companion so that's why this is working because our uh route group is chat which doesn't affect the URL our routes group is chat which doesn't affect the URL either so these are purely organizational and then we have a folder which affects the URL which is called chat and then we have a dynamic part of the URL called chat ID so that's why this is working great what I want to do now is I want to create a separate layout for this organizational chat folder so the layouts we're going to put inside is going to be the same as in this so it's going to affect only the routes inside of this organizational folder so go inside of your chat folder and create a new file layout.tsx like this and of course now you're going to get an error so make sure it looks like this in your organizational portal folder chat create a layout but outside of the routes folder like this now we have an error so we have to add an export here so let's go ahead and write chat layout like this and let's go ahead and extract the children prop inside and let's just give it some types so children is a type of react.react node like this and inside we're gonna go ahead and open a div and render the children like this so no no error and nothing has changed yet so let's go ahead and give it a class name of MX Auto Max width is for Excel like this and let's go ahead and give it H dash full and W Dash full like this great so now if you go ahead and expand your page you can see that has a big space on the side that's because we don't allow it to go further than a specific viewport great great job now we can go back inside of our routes chat chat ID page.tsx right here and let's continue our development here so what I want to do here is I want to load the companion which has this ID from our URL let's go ahead and let's give our chat ID page some interface so interface chat ID page props like this it's going to have params chat ID string so the same thing as we did with search params previously but the reason this is stored in params and not search params is because it's not a query like for example slash name a question mark name something that is stored in search Brands that's where we will find this value but when we have a dynamic part of ID like this that is stored inside params like that great so keep on this page and now we have this let's go ahead and let's extract the params here and let's assign these props chat ID page props like this great now let's go ahead and let's fetch the current user ID so extract user ID from house which you can import from add clerk slash next JS as I did on the top right here if there is no user ID in that case let's just return redirect to sign in from clerk nexjs as well like this perfect now we can safely fetch our companion so cons companion is equal to await Prisma DB which you can import from add slash lib Prisma DB and I'm just going to separate it from this top import because it's our local import prismadeb dot companion dot find unique let's go ahead and write where ID is params.chat ID like this so the reason it is chat ID is because we named our folder chat ID so make sure that in your types you have chat ID make sure that the folder is named chat ID and make sure that you use params.chatid here and we have an error because we cannot use a weight with a function which is not asynchronous so make sure you turn the chat ID page into an asynchronous error function like this and let's go ahead and let's add our include messages like this order by created at ascending so we are going to load all the messages relating to this companion but we are not just going to load all messages we are only going to load messages where we have the current user ID so we are only going to load messages between this companion and the currently logged in user and after this after these messages are object right here go ahead and add the count and this count is going to count all messages from all users because we're going to use that in the header so it's going to be the same thing as we saw on our home page but this time in the header so select messages true like this messages like this great now that we have that let's go ahead and check if we successfully fetch that companion so if there is no companion in that case return redirect to slash like this and make sure you import redirect from next slash navigation so this is where I added that so import redirect from next slash navigation and I'm moving it to the top right here uh great now that we have that we can go ahead and instead of returning this div we can return chat client like this and pass in the companion companion like this and of course uh we're gonna get an error because we didn't have that yet so inside of this folder chat ID create a new folder called components and inside create a new file client dot DSX like this great and inside of this client go ahead and Mark it as use client and Export const it's going to be called chat client like this and just return a div same chat ID client like that and then go back inside of your chat ID page and you can import chat client from dot slash components client like this so I did that right here perfect now that we have that let's go ahead and let's fix this type error so go back inside of your components client.dsx which has the the chat ID client and let's go ahead and write interface chat client props like this companion is a type of companion from Prisma client so make sure you import that and we also included something inside we included messages so that is a type of message again from Prisma client and it's an array and we also included a the count so underscore count it's an object with messages which is a number like this perfect so now that we have that let's go ahead and extract the companion like this and let's assign the props chat client props like this and now in our page there should be no type errors so let's continue developing inside of our chat client right here and let's go ahead and give this div a class name call H dash full b-4 and space Dash Y dash 2 like this so you can see how now it has a bit of padding on both sides and now we're going to go ahead and create our first component here called chat header like this and it's gonna pass we're gonna pass in the companion companion uh like this and of course if we save we're going to get an error so let's go ahead and let's go inside of our components folder and let's create a new file chat Dash header.esx like this great let's mark this as use client let's create an interface chat header props like this it's going to have a companion which is a type of companion from Prisma client and it's also gonna have messages which is a type of message from Prisma client and it's an array and it's also going to have a count object with messages as number like this great now let's go ahead and Export const chat header and return a div same chat header like this and let's just extract this props so companion and let's assign the interface chat header props like this great and now that we have the chat header we can go back inside of our app chat routes chat ID components client and import this chat header from add slash components chat header and I'm just going to separate this Imports and there we go we no longer have an error and we can go back inside of the chat header which is in our components folder right here great so now what we're going to do is we're going to add some classes to this div so class name is going to be Flex w sorry Lex W Dash full justify Dash between item slash Center order Dash B order Dash primary slash 10 and pb-4 like this so you can see how we now have a bit of a border here and so some obvious spacing that this is going to be a header great let's go ahead and remove this and let's write a div with a class name Flex Gap Dash X-2 item slash Center like this and go ahead and add a button from dot slash UI button so I imported that here or you can do it from components UI button and I'm of course going to separate it inverse let's go ahead and give this button a size of Icon and a variant of ghost like this and the icon which is going to have is Chevron left from Lucid react so make sure you've imported Chevron left from Lucid react like this and now that you have that let's go ahead and give this Chevron left some class names so h-8mw-8 like this great and now that we have that let's go ahead and let's add our router so constant router is equal use router from next slash navigation so no next router but next slash navigation make sure you don't till that mistake I'm gonna move it with the Global Imports okay and now that we have the router we can add and on click to this button so on click it's going to be a narrow function router dot back like this so it's just gonna go back to where we previously were so if you click back you should be redirected to the home page and then if I click on anything you should see this screen again and you can of course go back so just make sure you're in the chat client screen and let's continue developing this now so we have to create another component called bot Avatar let's go ahead and we have to add another component for that from chat CN so find the Avatar component like this and let's go ahead and let's find how we install it so we use this Command right here whoops I'm just going to copy it for npm version I'm going to go inside of my terminal right here I'm going to shut down the app and this is the command I'm going to run so npx schatzian Dash UI at latest add Avatar go ahead and press enter go ahead and confirm the installation and after that is done you can run Dev again great and just refresh your localhost of course great now let's go ahead outside of this button so after it and add a bot Avatar like this and of course we're gonna get an error because what Avatar does not exist so let's go and in this components folder create a new file bot-avatar dot DSX like this so it's in the same place where the chat header is great inside of this bot Avatar go ahead and Export const bot Avatar like this and let's go ahead and return a dip Avatar like that let's go back inside of our chat header which is in the components and let's import the bot Avatar so we are not seeing this error and I'm just going to modify the import to be slash components about Avatar like this create so no error and we have the text avatar so let's go back inside of this bot Avatar component and let's create an interface for it so interface uh both Avatar props source is going to be a string so very simple interface for it and let's go ahead and extract the source and let's just map it to bot Avatar props interface like this great and instead of using a div we're going to use an avatar from dot slash UI Avatar so make sure you don't accidentally import it from Radix like this and of course I'm going to replace this to slash components like that the Avatar is going to have a class name of h-12 and w-12 and inside we're going to use Avatar image from the same import AS Slash components UI Avatar like this and it is a self-closing tag which only accepts the source to be Source like this great so we have that ready let's go back inside of our chat header now and we have to pass in the source so source is going to be companion dot Source like this and there we go now we have a nice little bot Avatar so the reason I put that inside of components is because we're going to reuse it for the messages as well so this way we are prepared for the future great outside of this bot Avatar go ahead and create a new div with the class name of flex Flex Dash call Gap Dash Y dash one like this and create a new div inside with a class name Flex items Dash Center Gap Dash x dash 2 like that and create a paragraph inside which is going to render companion.name like this and give this paragraph a class name of font Dash bold like this outside of that go ahead and create a div with the class name of flex items Dash Center text Xs and the text muted Dash foreground like this inside of this div we're going to render the messages the Square from Lucid react so make sure you've imported that right here so we already have Chevron left and now we have messages Square which we added here let's go ahead and give it a class name of w-3h-3 and margin one like this and after messages Square we're going to go ahead and render companion dot underscore count dot messages like this so we're going to show the total number of all messages from all users in this header so user knows this is an active chatbot for example great now outside of this div and outside of this div so basically this div which is encapsulating the companion name and the messages go outside of it and let's go ahead uh and let's uh uh uh let's create a paragraph with class name tagslash access text Dash muted Dash foreground and it's going to render created by companion dot user name like this so now it says created by Antonio here perfect I really like how this looks great what we have to do now is we have to get our current user so let's go ahead and write const a user from use user from clerk next.js so we can use out we can use use user clerk has a lot of options for us and that's one more thing I love about Clark it has a bunch of hooks so you can get more information about the user or we could have used out from Clerk and then we would just get the user ID great so let's just do the use user for this case so I imported use user from Clerk and I have the use user hook here and I'm extracting the user great so we're going to go ahead and we're gonna go uh outside of this paragraph right here outside of this div and outside of this div as well so all the way to the last div right here go ahead and create a conditional if user question mark dot ID is identical to companion dot user ID only then are we going to render the following we're going to create a drop down menu and we already have that so we don't need to edit from chat CN we used it for our uh dark and light mode toggle so drop down menu from dot slash UI drop down menu like this so I imported it here and I'm gonna change the components drop down UI menu if for any reason you don't have this you can always go to chat cnui go ahead and find the uh a drop down menu and follow the installation instructions great so I'm gonna go back inside so only if we are the creator of this companion which we are because currently we are the only ones who created anything we are going to offer some additional options here so go ahead and add drop down menu trigger from add slash components UI drop down menu make sure you don't accidentally import from Radix because uh it's not going to work and you're not going to have any styling visible so go ahead and import this from UI drop down menu like this adopt a menu trigger okay and we're going to go ahead and give it a button which we already have imported and we're going to render an icon more vertical from Lucid react and we're going to go ahead and give this drop down menu trigger a prop as child and we're going to go ahead and give this button a variant of secondary and the size of Icon like this great so this is our drop down trigger and we already have button imported so just make sure you do as well great so now that we have that let's go outside of the drop down menu trigger and let's go inside of the drop down menu content from add slash components UI drop down menu so make sure you've imported that as well and make sure there is no Radix in your Imports and we're gonna give it a prop align and like this and let's go ahead and let's add the drop down menu item from add slash components UI drop down menu so one more thing you have to import from here great and let's go ahead and for the first one add an edit element so edit icon from Lucid react so make sure you import edit from Lucid react okay let's go ahead and let's give it a class name of W-4 h-4 and margin right 2 and let's write a text edit like this great so if you're not seeing this just go ahead and click so you can see besides that you can copy this entire drop down menu item and for the second one we're going to use the trash icon from lucidreact so make sure you have imported that I'm just gonna collapse all of this so you can see nicely in uh in one screen there we go so I have Chevron left edit message Square more vertical and trash from a lucid react great and instead of edit we're gonna say delete like this great so let's see what each of this has to do let's go ahead and let's we already have the router so we can use that for the edit because all the edit is supposed to do is supposed to redirect to our form but with the ID in the URL so we trigger the edit form so in this drop down menu item for the edit go ahead and add on click an arrow function to be router.push and I'm just going to expand my screen so you can see the entire code go ahead and open parenthesis add some back text and write a slash companion slash question mark sorry this special object companion.id like this so now if you go ahead and click on edit you should get the redirected or you can just refresh okay it worked but I I refreshed earlier so just click edit and you should get redirected to this form where you can edit your companion great so I'm going to go back inside of my home page I'm gonna click on the Ronaldo and I'm going to continue developing here we're also going to create a function for delete which now well won't do it won't do much yet but let's go ahead let's add our toast so const toast from use toast which you can import from dot slash UI use toast like this so this is where I imported it and I'm gonna rename it to slash components UI use those great so now that we have the toast let's go ahead and let's create const on delete function which is going to be an asynchronous arrow function with a try and catch block like this and in the catch let's go ahead and add a toast with a description of something went wrong like this and a variant of destructive like this and in the try block we're going to go ahead and run await axis so make sure you import axios I'm gonna go ahead and move it all the way to the top of my application like this so axis great so our weight axios dot delete open tactics slash API slash companions slash open a special object companion dot ID so the same route we are actually using to patch and edit our companions is going to be used to delete one great and after disk has been done we're gonna go ahead and open a toast with a description success like this great and now let's use this on delete for this drop down menu item which has the trash icon and the lit text so I'm just going to add on click on the lid like this so now if you try that we should get an error because that route does not exist so I'm just going to refresh this I'm going to click delete and there we go we got an error because that route doesn't exist so we just point it to a 404 page let's go ahead and let's resolve that error now so we have to go inside of our app folder I'm going to close everything now instead of our app folder inside of API inside of companion inside of companion ID and inside of the route and you can hear you should have the patch function so let's go ahead and let's add the delete function so export a synchronous function delete like this it has a request which is that request for the first parameter and the second one is going to be params which has uh an object and inside it holds companion ID which is a type of string so the same thing we did in the patch function you can also copy it from here to ensure that there are no typos like this there we go let's go ahead and open this let's open a try and catch block so in the catch let's get the error and let's just run console log it doesn't matter how you log this but I like to do it in this way so console log companion delete and an error so when I see this in my console I'm gonna know okay so something went wrong with the delete function in my API regarding the companion so that's why I do this you don't have to do this but I think it's very useful to have logs and let's return new next response internal error like this with a status of 500 like that great now let's go ahead and let's get our user ID so user ID from out which we already we don't have imported so let's go ahead and let's add out from add clerk next JS so I import that out from add clerk next.js here we use the current user because we needed a bunch of more information but we can use out to just get the user ID or you could also do the same thing you did here it's purely your choice great so now that we have that let's go ahead and check if there is a missing user ID so if we are not logged in in that case return new next response unauthorized and a status of 401 like this otherwise let's go ahead and delete this companion so const companion is equal await Prisma DB dot companion dot delete so you already have Prisma DB imported because you used it in the patch function it's right here so delete where we have the user ID and id params.continion id so what we did here is we only enable the deletion of a companion which is owned by the currently logged in user and the ID passed in the URL so no other user will be able to remove your companion only you will be able to move your companion right then let's just return nextresponse.json with deleted companion like this perfect so now that we have that let's go back uh here and let's try uh the deletion now I just want to see if we have to do some more things Yeah we actually have to do some more things so not in the API but let's go back inside of our chat header say inside of components chat header right here so we only did a toast of success if it's successfully deleted but let's also add router.refresh so we uh refresh all server components with the newest data even if it was deleted and router.push to slash like this great so save that refresh your page and let's try now so in my Prisma Studio let's see I have two categories so Cristiano Ronaldo three and Cristiano Ronaldo so I'm looking at Cristiano Ronaldo three and I'm gonna use the delete function and something went wrong again so let's go ahead and see uh what went wrong all right so the mistake is in our on delete function so take a look what I wrote in my uh API so I wrote API Companions and let's take a look at what we have so inside of my app folder API we don't have companions we only have companions so that's a mistake on my part let's go ahead and change the companions to just be companion like this save the file again refresh again so again I have two items inside I'm refreshing this and I'm clicking delete and let's see there we go success and it should redirect me back to the page you can see that I only have one now and I'm gonna refresh my Prisma here and there we go it's removed from here as well perfect so now you have a working header component for your chat next thing we're gonna do is we're gonna create an input component and then we're going to create an actual messages and all the necessary services for that great great job so far so before we move on and build our form and our messages screen what I want to do is fix a security issue we have with our edit functionality so right now whenever we are on this edit screen let's go ahead I'm going to close everything here and I'm going to go inside of my app folder inside of a root routes right here companion companion ID page so what's going on here well one thing we are not taking care of is that we are not checking whether this is the the user who created this companion so anyone can edit any companion right now that's not something we want so what I'm going to do right now is I'm going to add authentication in here and I'm going to pass in the user ID to this work loss and I'm going to do the same thing in the patch route in the API so let's go ahead and let's add const user ID run out and import out from ad clerk slash next JS so this is where I imported my out and I'm going to move it to the top like this and first let's check if there is no user ID so if there is no user ID in that case we can return redirect to sign in from clerk next JS as well so make sure you imported this as well great so now we have that solved and all I'm gonna do is alongside ID I'm also going to pass user ID like this so only the user who created this companion will be able to see this screen let's go ahead and let's test this so I'm going to test it by copying this URL so go ahead and copy the URL and try and use another account so I'm going to go ahead and I'm gonna use some other account let's go ahead and let's try this one okay uh I think I just found another mistake in our app uh let me try here so I just went back to localhost okay so I definitely didn't create Cristiano Ronaldo here we can see that because I don't have the three dots here but what happens if I go and paste this link there we go so it works because right now you can see that I'm not seeing the information about Cristiano Ronaldo so even though we have the correct URL we are not fetching that companion because we don't have the user ID and let's see what happened uh just a few seconds ago so I logged out and I went on a different website it seems yes you can see my URL is completely different okay so let's quickly fix that so go back to localhost 3000 and let's go inside of our navbar component so that is located in components navbar and in here we have the user button and yes I forgot to add after sign out URL to be a slash so that's one thing I forgot so let's try this again I'm gonna log in uh right here okay uh let's just test if edit works on the proper account so it does great and if I try log out there we go so I'm back in my actual application and if I log in I'm back inside great so this is what we were missing okay and one more thing we have to add is also the protection on the back end so in go inside app folder inside of API companion ID route so where we added our delete function you can see how here we are protecting that because we passed in the user ID but in here we are not so let's just go ahead and add user ID here as well and with that is actually mapped in user.id like this so great now even in the API it's protected and let's just check if that works so I'm gonna go in my companion whoops this is not the one I created so I'm gonna go and sign out and I'm gonna go inside the one that I did create right here okay so now I have these options I'm going to click edit and I'm going to change the Cristiano Ronaldo too and let's click edit your companion let's see if this is working there we go I got a success message and Cristiano Ronaldo too great so we fixed that security issue and now let's go ahead and let's create our form and our uh messages display so in order to do that I'm going to collapse everything and I'm going to go inside of my terminal right here inside of the terminal I'm gonna go ahead and npm install a package called AI so just this just AI like that npm install AI this is a package from Reversal and they have a really cool name for a package so just wait a couple of seconds for this to install and npm run Dev again great and refresh your application perfect now that we have that let's go ahead let's go inside of our app folder chat routes chat ID components client right here so make sure you are in chat client so only thing we render so far is the chat header before we continue and render the other stuff let's go ahead and let's add some hooks some states and some other functionalities that we need so const router is equal to use router from next slash navigation so make sure you import use router from next slash navigation and I'm going to move it to the top next one we need is the state so const messages set messages is equal to use state from react so make sure you've imported that and I'm also going to move it to the top like this all right and the type of the uh item items in that state is going to be a chat message props but we don't have it yet so let's just put any and an array like this and go ahead and just write companion dot messages like this there we go now that we have this let's go ahead and let's use this AI tool so I'm going to write const and empty array is equal to use completion like this and let's go ahead and let's import use completion from AI slash react so import use completion from AI slash react like this there we go so now it works the things we are going to extract from this use completion is input is loading angle input change handle submit and set input like this great and inside of this Hook is completion we're gonna go ahead and we're gonna Define what is the API call which is going to generate our AI messages so that is going to be a property API and go ahead and use backticks so it's going to be a route that doesn't exist yet but we're going to create it very soon slash API slash check and slash companion dot ID like this great the following options are going to be on finish which accepts The Prompt and the completion like this so it's a function but we are not going to use the prompt we are only going to use the completion so you do need to pass in prompt regardless you you cannot do just this because now this is the prompt so be careful it's in the second place right here and what we're going to do is we're going to create a cons system message like this and we're gonna go ahead and we're gonna put the Roll to be system like this and content is going to be completion so we're going to create a message which came back from our AI so this is going to be the AI message object and we're going to go ahead and we're going to add them to the current message array which we defined right here so set messages we're gonna use the current messages we're going to go ahead and spread current and add the system message like this and we're going to clear the input which the user wrote it's all going to make more sense when we add some other fields but we do have to finish this first in order to start developing other parts and after this is done I'm going to call router refresh so all server components are updated as well perfect so now that that is done let's go ahead and let's write our own submit function so const on submit as an event which is a form event which you can import from react so make sure you import form event form react go ahead and open point in Brackets and write HTML form element like this great and it is an arrow function so go ahead and write cons user message like this roll this user and content is the input which user made so same thing we did with the system message so what we're doing here is we are waiting for the response of our API which is going to generate the response of the AI model and then we store that as a system message and put it in the array of our existing messages but when we submit from this form we don't have a form yet but we're going to create an input when we submit that input we're going to create a user message and we're just going to set messages is the current spread the messages and add the user message at the end like this and handle submit and pass in the event like this and this handle submit comes from this use completion so you can imagine this use completion hook something like use form hook but we're not going to use react hook form in this part because we have this amazing package from Verso which is made for this AI tools so that's why we are using that great so now we have those two and let's go ahead and I'm just going to create a bit here and I'm going to write messages to do and now let's go ahead and let's create our chat form first because it's much simpler to do so inside I'm gonna go ahead and pass is loading to be is loading which we have from the use completion hook input is going to be input we have it from there as well handle input change same thing and on submit is going to be our custom on submit which we just created here great so if you save of course you're going to get an error so let's go ahead and let's go inside of our components folder and the same place where we created chat header let's create chat Dash form dot DSX like this let's go ahead and let's mark it as us client like this and let's export const chat form and let's just quickly fix the error chat form like this let's go back inside of that client where we use the chat form and let's import from add slash components chat form like this so the same thing with it with chat header so now we have the messages to do and we have the chat form great let's go back inside the chat form and let's create an interface for it so interface chat form crops like this input is going to be a type of string handle input change is going to be a function which accepts an event which is a type of change event which you have to import from react and it takes the HTML input element like this or change event which takes in html text area element like this and it returns a void like that great so I'm going to expand this so you can see it in one line and maybe just zoom out a bit so this is how it's supposed to look great now let's go to the next one which is on submit event is a type of form event which you can import from react as well and you pass in the HTML form element and in the other prop you have chat request options which are optional and there are type of chat requests request options and you have to import that from AI so import chat request options from AI like this or undefined like this and it's also a void great and last one is loading is a Boolean like this so I just zoomed out a bit so you can see that this is supposed to be all in one line I'm going to zoom in back now great uh yeah so make sure you have that and now let's go ahead and let's extract all of those props here so input handle input change on submit and is loading like this and let's go ahead and map this interface chat from props like this there we go and now in the client you can see we no longer have any type errors because all of the props are matching from the input from the handle change and for the on submit great now let's go ahead and remove this div and instead let's put a form element so a native html4 ML and on submit is going to be on submit and class name is going to be border Dash T border Dash primary slash 10 like this py-4 Flex items Dash Center and GAP Dash x dash 2 like this so I'm just going to collapse this like this there we go so we have those two and now go ahead and add the input from dot slash UI input or you can just change it to slash components like this as I'm going to do and I'm going to separate the Imports so let's go ahead and let's give this input a properly disabled is loading like this a proper value to the input on change is going to be handle input change placeholder is going to be type a message like this and last name is going to be rounded LG bg- primary slash 10 like this there we go great and let's go ahead and let's add a button so import button from dot slash UI button or you can change it to slash components like this inside of this button let's add a send a horizontal horizonal my apologies uh horizonal from Lucid react all right so make sure you have that let's go ahead and let's give this button uh a disabled prop of is loading and a variant of ghost like this and let's give this a class name of h-6 and w-6 as well there we go so now I'm just going to expand this a bit so you can see how this looks great now let's go ahead and let's create our chat messages component so we are done with the chat form now we have to replace this div with a new component called chat messages which also of course doesn't exist so chat messages make sure it's okay it's a multiple so messages great so companion is going to be one of the props so passing the companion it's gonna have is loading it's gonna have messages as well great and if you save of course uh you're gonna have this error so let's go ahead and let's create a new component so inside components not a folder but a new file chat Dash messages dot PSX like this let's mark it as use client and let's export const chat messages like this and let's return a div chat messages component like this now let's go back inside of our app folder chat routes components client and let's import this chat messages like this and Save and there we go so we no longer have our error so let's go ahead and let's go inside of our chat messages here and let's create an interface chat messages props like this let's go ahead and write messages to be for now any and an array we're going to change it later when we create another component and its props it's loading is going to be a Boolean and companion is going to be a type of companion from Prisma client like this great now let's go ahead and let's extract this props right here so messages by default is going to be an empty array is loading and companion like this and let's assign this props so chat messages props like that perfect now let's go ahead and let's create some styling so let's go ahead and write class name could be plex-1 like this so you can see how now it filled up the entire screen and our form is at the bottom exactly what we want and let's go ahead and write overflow X Y dash Auto so we want this to be scrollable scrollable and padding right is going to be a four just because when we get a scroll component I want to separate it a bit from the scroll bar on the right great so now that we have that let's go ahead and let's create another component called a chat message so not chat messages but chat message like this so of course we're gonna go ahead and we're going to get an error so let's quickly fix that and let's go inside of components and create a new file chat Dash message so singular.psx great mark this as use client of course and Export cons chat message like this and return it with chat message component like this now we can go back into chat messages and import the singular chat message from dot slash chat message but I'm going to use slash components and the error should go away now let's go back inside of this chat message let's go ahead and let's export interface so we have to export this one because that's what we're going to use in place of those any that I told you we're gonna change later so we're gonna use this chat message props like this so roll is going to be either system or user like this content is going to be optional and a string is loading is going to be an optional Boolean and source is going to be an optional string as well great so let's go ahead let's extract these props so roll content is loading and a source and let's map those so chat message crops like this let's go ahead and let's use the toast so const toast is equally used toast from dot slash UI use toast like that and you can replace that to slash components like this great and let's use the theme use theme from next slash themes so make sure you import that as well all right and now let's quickly create a copy function because we are going to be able to copy messages if they are written by our AI companion so const on copy is going to be a simple arrow function if there is no content to copy then just break the message because we can have a chat message component without content that's going to be a loading message right and let's just use Navigator dot clipboard dot write text and pass in the content like this and let's activate the toast which we used right here with a description of message copied to clipboard like that uh great so we have that now now let's go ahead and let's create and let's actually style our message right but before we do that let's go back inside of our chat messages here and let me just add some procs here so I'm gonna go ahead and I'm going to add a source to the companion dot Source like this I'm going to give it a row of system and I'm going to go ahead and write content go ahead and open this pointy brackets and backticks and write hello I am companion.name comma and companion.description like this so we're gonna have a fake message at the beginning great uh like this and let's go back inside of our chat message now and let's actually use that information uh to style so in this div go ahead and open a class name open curling brackets and import CN so we're going to have some Dynamic classes so C and for Malibu deals like this let's go ahead let's open this C and A a library and let's write the default classes which are group Flex items dashes items Dash start Gap Dash x-3 py-4 and W Dash full like this and now we're gonna have some Dynamic stuff so if role is identical to user so if we are the one writing this message I mean if this is our message in that case we're going to add a special class name justify Dash end because we want it to be on the left on the right side but if it's a robot speaking we want it on this side right great now let's go ahead and remove this text inside and let's go ahead and let's write if role is not user and if we have a source then we're going to render a bot Avatar component which we already created when we added the header so import that from dot slash bot Avatar or replace it to components like this okay we're gonna render that and we're gonna pass in the source to be Source like this there we go so you can see where we have this fake message now and it uses the avatar for the bot great and now let's go ahead and let's create a div here the class name of rounded MD px-4 py-2 Max with it small text small and BG Dash primary slash 10 like this and inside first we're gonna check whether we are loading so it's loading question mark let's do it like this so if we are loading uh in that case let's just write loading like this otherwise let's render the content like this there we go so you can see our initial message hello I am Cristiano Ronaldo too famous footballer great and one thing I want to change is this loading so I don't want it to say loading I want it to be an actual loading animation so let's go ahead and let's install a package for that npm install react Dash spinners and then npm run Dev again and just refresh your application and let's import a specific spinner so import beat loader from react Dash Spinners like this there we go and in place of this loading we're actually gonna have gonna go ahead and render the Beats loader which is a self-closing tag and we're going to give it a color depending on the current theme that's why we added this so if theme is light in that case it's going to be black otherwise it's going to be white like this so I'm just gonna collapse that like this because we're gonna have one more prop and that is going to be uh whoops and give it the size of five like this okay and now you can test that by going in chat messages and just add is loading to this one and there we go you can see how that looks very very cool right and now let's go ahead and let's copy this thing and let's paste it so we're just playing it right now and instead of a role being system role is going to be user and we're not going to use this Source we don't need it all right so just create a dummy message like this we're going to remove it later but I want you to see how that looks so now what we have to do is we have to create another component called user Avatar so let's go ahead and let's go inside of our components and you can actually go ahead and copy the bot Avatar component so copied paste it and rename it user avatar.dsx like this so mine is right here at the bottom user Dash Avatar and let's go ahead and let's rename this user Avatar instances to uh sorry about Avatar instances to user Avatar like this and it's not going to have any props so you can remove this instead it's going to use the user image so extract the user from use user hook from clerk next.js so you can see how much clerk is helping us and Mark this as use client and then we can just write user question mark dot image URL like this there we go that's it now let's go back inside of chat message so singular and what we're going to write after this div which renders either the loader or the content we're going to go ahead and add another conditional so if role is identical to user then we're gonna render user Avatar like this there we go you can see how my avatar is here now so I'm gonna go ahead and just modify this import to be slash components user Avatar like this there we go so here we added the bot editor because I want that to be on the left side of the message but if we are sending a message then I want my avatar to be on the right side so it actually looks better if you expand a bit like this great and one more thing I want to add is the copy clipboard button so if role is not user so if this is not a message from the user and if we are not loading this message only then I want to add a button from the slash UI button so make sure to import that as well and change it to slash components UI button like this only then I want to render this and let's go ahead and give it an on click to be on copy which we created already so we created on copy right here and let's go ahead and give it a class name opacity Dash zero group Dash hover opacity Dash 100 and transition like this size is going to be icon and variant is going to be ghost like this and we're going to use the copy icon from Lucid react so make sure you imported copy from Lucid react I'm going to move that to the top as well and let's go ahead and let's give this a class name of W-4 and h-4 so what does this group hover do well it does a very cool thing so you can see that now there is no button but when I hover look at this the button is right here and I can copy the message to clipboard and you can see it's the exact same message and we can only do that for the AI we cannot do it for our own well because it's our own right so how does this work well very very simple and it's actually a very cool effect so we have the parent div right here which encapsulates all of the items inside and we gave it the class name group and then on a very specific element here we said if we hover on the group meaning that if we hover anywhere on our parent right either on the image or on the this content wherever but as long as it's the parent div only then make this button visible because by default it's hidden by opacity so that's how we created that cool little effect great it and now that we have this chat message props let's first go back to chat messages and let's change this any to the chat message props and let's go ahead and let's import that from here as well so if this is not working for you make sure that you added export interface to chat message props like this great and now let's do the same thing inside of our client in chat ID so go here find the any we used and go ahead and add chat message props like this and make sure you've imported it from slash components chat message so singular don't confuse these two components they are similarly named but this one is multiple messages this one is just a single message so we have chat message props right here and now we also have to assign that type to the system message here like this so make sure the system message has a type of chat message props and the same thing for the user message here so just make sure you write that there we go no more errors now let's go back inside of our chat message is and what we're going to do now is we're gonna do a cool little loading trick for the initial message so right now this is how it's going to look you can remove this message for the current user right no need for that I only wanted to demonstrate to you so you can see what you're coding so this is the only message we're going to have in the beginning but I want to do a cool typing effect before the user actually before the AI actually renders this message right so this is all fake but it's I think it's a better user experience we are going to get to real AI messages in a moment but first I want to do this so let's go ahead and let's add const fake loading set fake loading it's gonna be use state from react so make sure you've imported that from react and we're only going to do this whole thing if we have no messages with this AI bot so if we already have 10 messages with it or even just one message we're gonna load those messages instantly but if this is the user's first time talking let's do a cool effect for a better user experience so if messages.length is equal to zero in in that case fake clothing is going to be true otherwise is going to be false so this is how it all looks in one line like this and let's go ahead and let's create use effect so make sure you import the new state and use effect from react and in this use effect I'm gonna go ahead and I'm gonna write the constant timeout is equal to set timeout open an arrow function and write set fake loading to be false so we're gonna have a fake loading which is gonna last one second so that's a thousand milliseconds right so we have this timeout and let's just make sure to clear that clear timeout timeout like this so make sure you clear your Timeout on unmount we don't want any overflow so clear timeout with the capital T Pascal case uh and now let's use this fake loading for our first message right here so fake sorry so is loading is going to be big loading like this so I'm going to refresh and show you how this looks now you can see how for one second it's loading I just think it looks kind of cool and it's this is only gonna happen if this is the user's uh first time chatting so they don't have any messages yet right so only then is it a cool effect later it will be annoying but I think for a user visiting for the first time you know let's go ahead and let's just load this for a user visiting for the first time and clicking like this I think it's a cool effect great so make sure you're in this chat we're not done with the chat messages component yet uh what I want to do now is I want to render the actual messages so this is a fake message right here but let's now render actual messages so messages.map we have the individual message and let's go ahead and let's uh render chat message which we already have imported let's give it a roll of message role of message dot row uh let's give it a key of message.id uh or sorry message that content and let's give it a content of message dot content let's give it uh oh and let's give it a source of message.source like this there we go so this is going to render all of the messages both from Ai and both from us right so messages.net we have the key which is content we have a role which is roll and we have content which is content and source which is Source make sure you have all of these props right here and we're going to add another one and that is is loading right here so if we are loading that means that the AI is generating a message so while that is happening I want to render another fake chat message and all it's gonna do is gonna have a roll of system and it's going to have a source of companion.source oops so companion dot source and is loading State like this so that is gonna happen uh but you're gonna see later so example you can change this to True maybe now we cannot control it like that but okay you're gonna see it later how it's gonna look basically or you can just comment it out or just reverse this so you can see this is how it's going to look while the the API route is generating this is how it's going to look for us so just make sure you remove this exclamation point so is loading and and chat message like this uh great and one more thing we have to add is we have to add a ref which is going to be used to scroll to latest messages imagine if we have a thousand messages in here right we don't want to always scroll all the way to the bottom so we're going to create a functionality which is going to do that for us so let's go ahead above this state and let's add const scroll ref to be use ref from react so make sure you've imported that and let's go ahead and let's give it the type of element ref which you can import from react so I've imported it right here element ref and go ahead and open pointy brackets and in annotations write div like this and by default is going to be no great now let's go ahead and let's actually create that div so that div is gonna be all the way at the bottom right here it's a self-closing tag and just give it a ref of scroll breath like this there we go that's it so that's the element that we're going to use to scroll all the way down so every time a new message appears or if we load all messages by just visiting this chat we're gonna scroll all the way down for the users so they don't have to do that and let's just go ahead and let's add a use effect which is going to actually do that so use effect let's add the empty array here and let's just write scroller ref question mark dot current question mark dot scroll into view and let's give it the behavior smooth like this and it's gonna happen every time the messages length change so messages dot length inside the dependency array don't forget this part great great job so you finished all UI regarding the chat so you can now go ahead and close everything go inside the app folder chat routes chat ID components client right here there we go you finished everything here and you can actually test it let's see if we can add a message so it's obviously going to fail from the API side but let's see if this is enough to just add our message so our message for example there we go you can see how this looks and you can see how for a second the API was loading but then it failed because we don't have an API route which we are aiming for right here so we don't have API chat companion ID yet but we are going to create that in the next part so we are very close to finally seeing some messages from here but yeah you can play around and add some messages you can see how that looks great great great job so far so in the next part we're going to go ahead and create the memory service and all the necessary things we need in order for AI to generate our messages back so it's time for us to create this route slash API slash chat companion ID but before we can do that we have to install some packages and we have to create a memory service before we continue any of those so first thing I want to do is I want to install all the necessary packages which we're going to need to create a memory service let's go ahead and let's shut down the application and let's install npm install at Pinecone Dash database slash Pinecone so this is going to be the vector database which you are going to use to create embeddics in order to make an advanced AI model great now that we have that let's go ahead and let's run npm install at upstash slash redis so this is going to be a redis database which we're going to use for long-term memory and let's always install npm install apps slash rate limit we're also going to use app stash for late rate limiting so no users can spam our AI models more than they should great and let's also go ahead and let's install length chain so npm install line chain like this so just wait a couple of seconds and after this is ready we're going to go ahead and we're going to create some accounts and add environment variables great so now that we have these three or four packages installed let's not start the application yet and let's actually go ahead and let's close everything and I want you to prepare your environment file because we're going to be adding a bunch of new stuff inside so first website I want you to visit is pinecone.io so you can either Google Pinecone Vector database and click on the first result so Pinecone a vector database for Vector search or you can just visit pinecone.io go ahead and create your account so I'm gonna go and log in now and this is how that will look like but for you you might see a message project initializing and that can take up to five minutes so just be patient and refresh your page every now and then until you get this welcome to your starter project let's get started so first thing you want to do is you want to create an index so let's go and let's give this index a name so I want you to do exactly the same as I do so we're going to give this a name of companion like this so lowercase companion this is important because that's what we are going to put in our environment variable now for the dimensions go ahead and run and write one five three six like this and you can leave the metric as it is and go ahead and select the starter which is the free type like that so no monthly cost this is completely free so important that your index name is companion and that the dimensions are one five three six and click create index like this once that is done you're going to see a screen similar to this so let's go ahead and let's prepare the environment variables which we need right here so first one which we need is Pinecone underscore index like this let's go ahead and let's give this an index of companion like this because that is what we named our Index right here so companion like this great besides that let's also go ahead and let's add Pinecone underscore environment like this and leave this empty for now and let's also add Pinecone underscore API underscore key like this so now let's go ahead so I created a couple of typos in this environment variables here on the screen I showed you the correct environment variables but you don't have to fix them now because we're going to fix them together later and find these two variables so I'm going to expand my screen back and in this screen you have a big connect button so you can either use the connect button or you can click on your API keys right here so I'm going to do it using the connect button here and make sure you select your language node I think by default it's python so make sure you change it to node like this we already have the client so what we have to do is we have to add these two so you can see that my password I mean my API key is hidden so just click on this eye icon right here and that's gonna make it visible so let's copy the API key like this I'm gonna go back inside of my environment variables and I'm gonna paste it here and let's do the same for environment so this Asia Southeast like right here go ahead and copy that and paste it here just like that great now that Pinecone is set up you can safely close all of the Pinecone related tabs and where I want you to go now is to upstash.com or you can Google upstash so just go ahead and log in so I've logged in here and you're gonna see a screen similar to this so this is a completely new account go ahead and find the button which says create database so go ahead and create a database let's give it a name of AI companion right here let's put the type to Global our primary region can be anything you want and read regions well something else like this so you can choose this tip and you can go ahead and select SSL and addiction and click create like this and the database is being prepared so let's just wait a second and there we go what we have to do now is we have to scroll all the way down to rest API so find this section which says rest API so not this first we know gonna need anything of this we're gonna need the rest API right here and go ahead and select the dot environment right here and go ahead and click on the I or click on the copy so you need these two variables app stash Regis rest URL and up stash radish rest token so I just copied this to in the rest API column and I'm going to go ahead and paste them in my environment variable like this there we go so now I have my upstairs URL and my upstairs rest token great great job so one more API key that we have to add is the open AI API key because we are going to use open AI to generate embeddings regardless of the model we want to use so we are actually going to use the Llama model from replicate AI but we do need an open AI underscore API underscore key this is going to be to create embeddings great so let's go ahead and let's go to the open AI platform so you can Google open AI platform and create an account and in here go ahead and click on view API keys right here I'm gonna go and create a new secret key called AI companion tutorial for example and I'm going to create a secret key and once you see it right here you have to immediately copy it because you're not going to be able to see it again so copy the key right here go back inside of your environment variables and just paste it right here there we go and then you can safely close this tab and you can go ahead and close the open AI but I just want to mention so you can always click on the usage right here to see how much you have left right so open AI has a free tier but if you see this in Red Line it means it has expired goes to say open AI is very very cheap so I this is my second project using open Ai and I test this intensively and I barely spent 12 dollars so you can see for yourself whether you would like to add a some credits here or not but if you see an option that you have five dollars and it's a gray line that means you have a free tier so feel free to just use the free tier great so I'm going to close this stuff now I'm going to close the up stash we are successfully connected um to everything inside great so we can now go ahead and create the memory service so let's go ahead and let's go inside of our let's close everything first let's go inside of our lib folder and let's create a new file called memory dot DS like this so this is going to be a very long file so if you want to you can go inside of my GitHub and carefully watch while I code to see if your code is the same this is going to be complicated and any small mistake you can make the model not work properly so you can go ahead and visit my GitHub and find the equivalent file and just look at it so basically you can go right here in my GitHub find the lib right here find memory you didn't have to copy it so you can code along with me but this is what we're gonna create right now you can see it's a very complicated piece of code so just be careful and if you're not sure you can always just copy it and paste it but I'm not gonna do that we're gonna go step by step and we're going to create this memory class so let's go ahead and let's add all the necessary Imports so let's import redis from at upstash slash redis like this let's go ahead and let's import open AI open AI embeddings from line chain slash embeddings slash open AI like this let's go ahead and let's import the Pinecone client so pinecon client from at Pinecone Dash database slash pine cone like this let's go ahead and let's import uh python store from Lang chain slash Vector stores slash pine cone like this great now let's expert type companion key it is an object which has companion name which is a type of string model name which is a type of string as well and user ID which is a type of string great now let's go ahead and let's create the class memory manager so export class memory manager like this let's go ahead and let's give it a private static instance which is going to be a type of memory manager like this let's go ahead and give it a private history which is going to be a type of radish let's go ahead and let's add private Vector DB Vector DB client which is a type of Pinecone client like this let's go ahead and let's create a public Constructor and let's add the history and the vector DB client so this dot history is equal to redis Dot from environment like this let's go ahead this dot Vector DB client is new pine cone client like this there we go now let's go ahead and let's create the initialization function so public asynchronous init function is going to check if this dot Vector DB client is instance of Pinecone plant and let's await this dot Vector dbclient dot init and let's pass in the API key which is process.environment.pincome underscore API underscore key like this and you can either put dash dash empty string actually don't do that you can just put an exclamation point at the end like this and also add environments roses dot environment dot Pinecone underscore environment like this so just add these two and be sure to cross check them with your actual environment file so we have the Pinecone API key you can see that I have a type typo here so I put pin cone instead of pine cone so it's a mistake that I made so make sure that your dot environment file and this init file uses the same environment variable so I'm going to copy them from environment and I'm going to paste them here to confirm that they are the same so Pinecone API key Pine coin API key you can see how a small mistake would make my entire project not work so make sure that it matches great now that we have that let's go ahead and let's create a function for Vector search so public asynchronous function Vector search like this it's gonna take recent chat history prop which is a type of string and it's also going to take a companion file name which is also a type of string like this now let's go ahead and let's actually get our pinecon client so const Pinecone client is equal so let's go ahead and open pointy brackets Pinecone clients this dot Vector DB client like this now let's go ahead and let's get the index so const pine cone index is equal to pine cone pycon client dot Capital index like this so capital I make sure this is not lowercase and go ahead and write process.environment dot Pinecone underscore index like this and add a pipe pipe empty string like that and again cross check that the spinecon index is actually in your environment variable you can see another mistake I made so pincon it should be Pinecone so please make sure to cross check your environment variables right here great so pine cone Index right here pine cone index Pinecone environment pinecon API key great now that we have that let's go ahead and let's get our Vector store so const vector store is equal to a weight pine cone store Dot from existing index like this new open AI embeddings let's go ahead and open an object and pass in the open AI API key to be process.environment dot open AI underscore API underscore key like this and let's also pass an object which has the pine cone index inside so again let's cross check that we have the open AI API key in our environment so I'm going back to my environment and looks like this is correct so I had two problems with my Pinecone environment keys please make sure that you check them as well because the errors can be quite cryptic great so we have that now now let's find similar documents so we're gonna look through the memory and in this Vector databases to see if we can find something similar saw const similar dogs is going to be a weight Vector store like this dot similarity search let's go ahead and pass in this prop recent chat history inside the second argument is going to be uh three and then we're gonna pass in an object file name to be companion file name like this great and now let's pipe that with a catch function let's get the error and let's go ahead and let's cancel the log failed to get Vector search results and pass in the error like this great then at the end remember to return similar Docs to this function right here great now that we have that let's go ahead and let's create a function to get the instance so public static asynchronous function get instance like this it's going to return a type of promise and inside a memory manager like this so let's go ahead and let's first check if there is no memory manager that instance in that case let's assign a new one so memory manager dot instance it's going to be new memory manager like this and let's just await let's await memory manager dot instance dot init like this so we created that init function right here and now we are calling it in this get instance function great so we have that if Clause finished now let's go ahead and let's just return memory manager dot instance right here there we go now that is resolved great now let's go ahead and let's create a function to generate radius companion key so that's how we're going to store it in the database so this is going to be a simple one private generate redis companion key go ahead and take the prop companion key which is going to be a type of companion key so let's go here so oops I have a typo so in my export type I I accidentally put commandion key so it's supposed to be companion key like this make sure you don't do that type so this is what I was telling you there's a lot of stuff going on here and uh even the smaller stuff can make your project not work so let's go ahead and add the return function which is going to be a string let's go ahead and let's just return some backticks and let's use the companion key dot companion name as the first part then use a dash and connect it to companion key dot model name and let's use another Dash to connect it to Unique users a companion key dot user ID like this so I can can I zoom in even more there we go so generate the red is companion key uses companion companion name Dash companion key model name Dash companion key user ID like that make sure you return that string great now let's create a function to write the history so our models can adapt to new information so public asynchronous function right to history it's going to accept a text which is a type of string and a companion key which is a type of companion e like this let's go ahead and let's check if we don't have the companion key or if we don't have the user ID so if there is no companion key or if type of companion key dot user ID is undefined like this in that case let's comes a log companion key set incorrectly so we know what's going on and let's just return an empty string like this great but if that is not the case let's go ahead and let's generate the key so const key this Dot and we're going to use this private function so this dot generate redis companion key and let's pass in the companion key inside like this and now let's write once the result is equal to await this dot history and our history is an instance of Radice so we're going to use a redis function dot Z ad like this let's pass in the key so we're gonna store this under this specific string which uses the companion name which would be for example Cristiano Ronaldo then the model name which in our case is going to be a lamba and then the user ID which is the currently logged in user so a unique history for this user that companion and that model so that's the key we are passing and now let's pass some options so score it's going to be date dot now like this and uh member is going to be text like this great and let's return result like this there we go now let's go ahead and let's create a function to read from the history so public asynchronous function read latest history like this again we need the companion key to access it and that's a type of companion key like this and it's going to return a promise which is going to return a string like this so let's go ahead and let's check if there is no companion key or if type of companion key dot user ID is undefined again console log again companion key set incorrectly again and we'll just return an empty string like this if that is not the case let's go ahead again and let's generate the key so const key is equal to this dot generate redis companion key and pass in the companion key again because the same way we needed it to write to history we also needed to fetch the history using redis so now that we have this uh key let's go ahead and let's write let result because this is a constant that might change so let's result await this dot history dot Z range so this is another function from redis let's pass in the key the second argument is zero and third is date dot now like this and let's go ahead and let's pass some options by score true like this great and now let's go ahead and let's add uh let's modify this result to be result dot slice minus 30 dot reverse like this so we are doing some special modifications over this result constant to get the results we need and we're going to use some tricks by um splitting the text because the way it's stored in the database the way it's stored in Vector databases is different than what we need in order to actually work with it so we have to do a couple of tricks like this so let's go ahead and let's write const uh recent chats to be equal to result dot reverse dot join and let's go ahead and let's pass in um this back backwards slash and like this and let's just return recent chats like this great now let's go ahead and let's create the last function which is going to be a function to see the chat history so that's the what we're going to use you know in the Croatia in the uh companion creation we have two fields we have the instructions and we have the seed chat the example conversation so we're going to use that example conversation to actually seed that information into a vector database so we can create a memory for our companion so it knows how to behave and create better results for the user so public a synchronous function seed chat history it's going to accept seed content which is a type of string it's gonna accept a delimiter which is a type of string and by default is going to be a backward slash and like this and a companion key which is a type of companion key like this let's go ahead now and let's create const key again this generator redis companion key and pass in the companion key like this if await they start history so if in redis there already is a key which we just created that means that we already created a history for this uh AI companion and no need to do it again so console log user already has chat history like this great and just break the function otherwise let's go ahead and let's create const content to be seed content dot split by the limiter let counter is equal to zero now let's go ahead and let's write a for Loop so four const line of content wait this dot history dot Z ad e and let's go ahead and let's pass in its core which is counter and member which is line like this and let's increase the counter by one like this there we go we finished the entire memory manager class I know this was a bit complicated this is also quite new to me so I'm doing my best uh to wrap my mind around what's exactly going on but we are learning together these are interesting Technologies we are working with so again if this is complicated if you think you might make a mistake in the environment variable or you might make a mistake in any of this bunch of functions you can always just commonly visit my GitHub don't worry about it and you can just copy the working code from here so this is guaranteed to work just make sure to uh double check your environment variables of course so that's very important those that's one of the places where errors can hide so make sure you cross check your environment variables if you decide to copy for my GitHub and if you wrote it by yourself either way great great job so now that we have the memory we are ready to create our API route to create some chat so now that we have created the memory for our AI companion let's go ahead and let's actually create the chat route so before we do that one more thing we have to do we have to add our rate limit lib and it's going to be very simple so let's go inside and let's create rate Dash limit dot TS like this and let's import rate limit from at upstash slash rate limit like this and let's go ahead and let's import redis from Pub stash slash redis like this and let me just quickly see what is the remedy oh yeah so it's rate limit like this so I accidentally put a capital l but it's lowercase l so rate limit like this and let's export a synchronous function rate limit like this with an identifier which is a type of string and let's add constraint limit to be new rate limit and let's go ahead and let's add the options so redis is going to be redis Dot from environment like this limiter is going to be rate limit dot sliding window 10 seconds so this is this will allow the user to make 10 requests within the 10 second window anything about that is going to be blocked analytics you can turn that to true and prefix is going to be at upstash rate limit like this and return a weight rate limit dot limit identifier like this perfect so we have that now and now let's go ahead and let's create our chat API route so I'm going to close everything and I'm going to go inside of my app folder inside of API and I'm going to create a new folder called chat like this and inside go ahead and create a new folder which is going to be a dynamic part of the API URL chat ID with capital I like this and inside go ahead and create route.ds like this so let's go ahead and let's uh and let's add some imports so import streaming text response from Ai and long chain stream from AI let's also import out and current user from at clerk next JS let's go ahead and let's import callback manager from blank chain slash callbacks like this let's import next response from next server like this let's import memory manager from add slash lib slash memory let's import rate limit from s slash lib slash rate limit which which we just created and let's import Prisma DB from s slash lib Prisma DB and before we continue let's go ahead and let's install one more package that we need so let's go ahead and run npm actually we don't need to install anything my apologies we already have Lang chain I forgot so we also have to add import replicate from Lang chain slash llms slash replicate like this great so now we have replicate perfect and before we actually build this uh entire thing I want you to create a replicate uh AI key so let's go ahead and let's prepare that in our DOT environment first so just below open AI key go ahead and create replicate underscore API underscore token like this and I want you to visit replicate.com or just type replicate by Ai and choose the first link create your account and go ahead and go inside of your API tokens and go ahead and create a new token let's call this companion Dash tutorial create a token and just go ahead and copy that token and then you're gonna go ahead and paste it right here so replicate API token like this great so that is done now let's go back inside of here and let's go ahead and let's write export a synchronous function post like this which has a request which is a type of request and Rams which have the chat ID so params is an object which has chat ID which is a type of string inside great now go ahead and open a try and catch block inside let's go ahead and let's resolve the error first so in the error we're gonna return new next response internal error with the status of 500 and for development let's also add a console log chat post and let's log the error so this is going to help you in development if something goes wrong you're gonna see that in the terminal with the log chat post so you know this is the file where something went wrong so let's go ahead and let's extract what we need from our request so const prompt is equal await request.json like this now let's get the user so const user await current user which we imported from Clerk now let's go ahead and let's check if any of the user information is missing so if there is no user or if there is no user DOT first name or if there is no user.id in that case return new next response unauthorized with a status of 401 like this great now let's go ahead and let's create an identifier for our rate limiting so rate limiting is going to be unique and uh for every single user right we cannot block the entire API because just someone made too many calls we have to block that exact user so const identifier is going to be request.url plus Dash plus user dot ID like this and now let's go ahead let's extract success from await rate limit which we imported from the identifier like this there we go if it's not success in that case return new next response rate limit exceeded with a status of 429 like this meaning too many requests great now that we have that let's go ahead and let's immediately uh update our companion messages with this prompt because this prompt is what we will going to we are going to write here and press send so that is going to be this prompt so let's immediately update our companion so Collins companion is equal await Prisma DB dot companion dot update where ID is params.chat ID where user ID is user.id and data messages and let's go ahead and create a new message in that relation with the content of prompt role of user because we are sending the message and user ID of user.id like this great so now let's go ahead and let's quickly check if there is no companion in that case go ahead and return new next response companion not found status 404 like that great now that we have that let's go ahead and let's generate some constants and some file names which will be needed for the memory manager sarcon's name is equal companion.id so we want the name to be unique let's just not make this typo let's generate the companion underscore file underscore name to be name plus dot dxt like this now let's go ahead and let's create the companion key so const companion e is an object which has the companion name which is this constant name so the ID like this and then we're going to have user ID to be user.id and model name is going to be llama so Lama 2-13 B like this great and now let's get the memory manager so const memory manager is going to be a weight memory manager dot get instance so the function we created now let's go ahead and let's read the records using this companion key let's see if any memory for this companion already exists so const records a weight memory manager dot read latest history companion key like this if records dot length is zero that means that there are no records inside and that we have to seed the chat history right with that example conversation so let's do that await memory manager dot seed chat history companion.seed so that's let me show you what field that is so we're gonna use uh let's actually use edit oh yeah I'm not running the project uh well yeah we can run the project I don't think there's going to be any uh problems so npm run Dev I'm just going to pause a bit for the project to run all right so we are using this Field's instructions and example conversation to add them to the initial memory so we create the behavior of our Ai and we are using memory manager seed chat uh to get that seed content and we're using it line by line because if I remove this you can see how it's supposed to look line human line Elon human Elon or whatever your uh AI is and it's going to go line by line and add that to history so that's why it's important to write in that format and now we're going to pass that here so we're using companion.seed so this conversation example conversation is mapped to seed in the database so we're using companion dot seed let's go ahead and let's add the the limiter so this is going to be uh backslash n and backslash and again and passing the companion key like this with a capital key like this there we go okay so we are right here perfect so that's if we don't have any records uh and now let's go ahead and uh write to history with this new prompt that the user just wrote so yes we do save it in the database but this is just our Prisma database this is just to keep uh the history of those messages it's not related to the actual memory of the AI this is just for our database to show to the users right so we're having multiple databases here this is a very Advanced AI project so what we have to do now is we have to add the same prompt which we just added uh into companion using this new message relation we have to do the same thing but for our Vector database so let's go ahead and let's rate a weight memory manager dot right to history user question mark sorry user this double dot space make sure you put the space here plus prompt plus go ahead and add the backslash n and companion key like this great so now we've wrote that to history as well now let's go ahead and let's fetch the recent chat history so const recent chat history is await memory manager dot read latest history and pass in the companion G again so we can read for this exact user and that com and that uh companion great now let's get the similar docs so cons similar dogs weight memory manager dot Vector search recent chat history and companion underscore file underscore name like this great now let's go ahead and let's find the relevant history so this is just more information to make this AI model as good as possible so let's go ahead and let's get uh let relevant history to be an empty string at the beginning then let's add an if there are no similar sorry if similar docs is a Boolean and uh if similar docks at length is not zero like this in that case let's go ahead and add the relevant history to be similar docs.nap let's get individual document and get document.page content like this and let's join it with our delimiter so backslash n like this so we have to do this uh weird things because we are working with a very specific prompt and Seed chat right so that's why we're using this line breaks as the limiters and we join by them and we split by them all right now that we have that let's go ahead and let's get our handlers so const handlers from Lang chain stream like this and now what we have to do is we have to create our model so const model is equal to new replicate like this and now you have to pass in the model so there are two ways you can do this you can either go inside of my GitHub and go ahead and find the app the API the chat chat ID and route and you can just copy my my model right here so you can see it's a very long line and that's why I'm telling you to go ahead and copy it right so you have to copy it from somewhere either from my uh GitHub or you can go ahead and search for this exact thing so this uh sorry let me zoom in a16z in for a llama to 13B chat so you can go ahead into replicate click explore and go into search and find that chat right here go into API and you're gonna see it you're gonna see it right here right but I'm going to use the one from my GitHub because it's uh well I know that it works right there are different models you can play around in replicate find different uh uh llms but this is the one that I know works so I'm gonna use this one uh I don't like taking you always to my GitHub I know it's annoying but you can see that you just simply have to have this version in order to have the exact same results that I am so let's just quickly steal this from my GitHub this exact model so it's line 88 uh at least I think it's 88 yeah in my file chatidrout.ts so the same file where we're working right now so I just copied that model right here and I'm going to go ahead and paste it like this there we go so we have the model now and now let's go ahead and let's add the input object let's add Max underscore length to be 2048 like that let's add the API key to be process.environment.replicate underscore API underscore token and Let's cross check that in our environment so replicate API token replicate ABA token it seems to be the correct one great okay and now let's add the Callback manager to be callback manager Dot from handlers handlers like this there we go and let's also enable the verbose so model dot verbose to be true so this is going to add useful logs to our console great and now let's go ahead and let's add some more instructions for our AI model so const response is equal to a string open and open brackets like this and write a weight model like this let's chain dot call and I want you to go ahead and open back ticks like this and we're gonna write a prompt inside so we're going to write only generate plain sentences without Graphics of who is speaking do not use open this curly bracket and write name prefix like this so you can either do this exactly the way I am or again I I would actually recommend you to go inside of my GitHub and copy this response object right here right so you have to edit this Little Instruction so it so it behaves the same as mine right but I'm gonna write it out just to uh to kind of explain what I'm doing right so it should be in one line like this only generate plain sentences without prefix of who is speaking do not use so this would be for example Elon prefix because there is a chance that your AI responds like this with Elon I am the CEO right so we don't want that we don't want it to say Elon we just wanted to say I am the CEO right so we could either omit that through you know splitting the text or we can instruct the AI do not do that right and just below that let's go ahead and let's insert the companion dot instructions so that's going to be these instructions right here which right now we have a dummy test uh dummy text right but here we're going to explain the behavior for example you're a fictional character whose name is Elon you're a Visionary entrepreneur and inventor so that's what's going to be passed here great and now let's go ahead and write below are the relevant details about name past and the the conversation you are in and let's go ahead and let's pass in the relevant history so now we are inserting that memory which we just fetched from a vector database into this prompt right here and let's also add all chat history so recent chat history let's add uh a uh line break like this and let's add the name like this great so that is done and let's just change the catch console.error so again if this is confusing I know it's a bit weird but we have to do it in this way so you can just visit my GitHub and copy this from my uh so go ahead and find the API chat chat ID route TS and just copy this response right here so you have the exact same prompt that I have great now we have that response and now we have to do some more uh cleaning and replacing through this string because uh moving it through redis through Vector database and then all the way to what uh this our use conversation hook on the front end can handle requires a bit of work so let's go ahead and let's do that so const cleaned it's gonna be response dot replace all so we're going to replace all commas with empty strings because for some reason the model responds with a lot of commas and it just looks weird and now const chunks is going to be cleaned dot split and let's use the delimiter backwards slash n like this and const response is Chunks zero like this great now let's go ahead and let's write this response to memory manager so await memory manager dot right to history go ahead and add an empty string empty string plus response.shrimp which is a function and companion key like this great now let's go ahead and let's create our readable Stream So VAR readable is equal require string dot readable like this great now let's add a constant let s is new readable s dot push response so I'm going to zoom in a bit so you can see so as that push responds as that push null like this and let's go ahead and let's check if the response is correct so if response is not undefined and if response.length is larger than one meaning that the response from the AI is correct let's go ahead and let's write memory manager dot write to history empty string plus response.shrimp like this and companion key like that great and now let's go ahead and put await Prisma BB dot companion.update so now we have to update our message so where id params.chat id like this and let's pass in data messages and let's create a new one so create content response dot trim like this roll this system and user ID is user dot ID like this great so we have that done and all we have to do now is return new streaming text response and passing the S variable which we created right here and one thing I noticed that I did wrong uh was uh go all the way up to where we first update our companion so do not pass this user ID to the wearer so that's a mistake we don't need that this is on this is going to mean that only uh the user who created the companion can chat with him we don't want that so I've had this user ID user.id on line 30 right here so in the beginning of this post function just remove that we're not going to need that for here and we're also not going to need it for the bottom right here uh great so you successfully finished the API chat and before you try it out I actually want you to go ahead and I want you to remove any of the characters you have so I'm gonna go ahead and remove this one so no need for it and now we're gonna go ahead and create a new one and this one is going to have proper instructions so let's go ahead and let's do that now so let's go ahead and pick an image for your companion so I'm gonna go ahead uh and I'm gonna use Albert Einstein for this one so I'm gonna create a a companion for Albert Einstein again if you want these cool images you can generate them using hotpot.ai I'm going to link the the link is in the description so let's give this a name of Albert Einstein like this and let's give him a uh well let's call him famous math like this I know he's a theoretical physicist or something but I don't know how to how to write that okay now in category let's go ahead and let's select the scientists like this or whatever category you put and now for the instructions and example conversation what I want you to do is I want you to go inside of my GitHub and I created a folder for you called Companions and in here you can find a bunch of examples so I'm going to use Albert so this Preamble is the instruction so I'm going to copy the Preamble and I'm gonna paste it in the instructions like this so you're Albert Einstein you're a renowned physicist known for your work uh known for your theory of relativity right so let's go ahead and let's also add the example conversation so I have that right here so go ahead and copy that so that is the seed chat so I'm going to go ahead and copy this and I'm gonna paste it so there we go hi Albert what's on your mind and Albert responds like this and let's create the companion with this proper information now great so we did that now and now we're gonna enter here and we're actually gonna go ahead and we're going to test uh this API so let's see and let's go inside of our uh uh well where is this it's in app folder chat routes chat ID components client so we are going to slash API slash chat companion ID and I suggest that you keep your terminal open for this so you can see if there are any errors popping up so we did a lot of stuff right now and I'm going to close my Prisma studio for now so we did a lot of stuff any any amount of Errors could happen so I'm going to keep my terminal open and I'm gonna attempt to send a message uh for example saying uh hi what is your favorite equation for example uh okay something's going on okay uh I got this error so let me see what exactly uh this is regarding all right so let's go ahead and let's install a couple of packages which I think might fix this so let's install mm install replicate like this and let's go ahead and let's run and install open AI like this and let's go ahead and let's install npm open AI Dash Edge like this so just a couple of packages that I think this Lang chain might use as pure dependencies or something and let's run Dev again and let's see if maybe now our uh our uh our error is going to be fixed so I'm going to refresh here again okay I'm keeping my terminal open [Music] let's wait a second for this to load okay let's try again hello what is your favorite equation okay no errors so far let's see if this is actually gonna uh okay looks like you can is here you can see the instructions so only generate uh only generated plane sentences without prefix of who is speaking you can see the entire history there we go look at this Greening ear to ear I'm a sucker for good formula but my all-time favorite equation has to be E equals m c squared there we go we successfully created a very smart AI model so if you have any errors make sure that you're looking at them in the terminal so the errors could be in your environment variables it can be in the missing packages or it could be in some issue with the memory manager so you can always visit my GitHub and just cross check my code to yours see if everything is correct if you truly think you have an error which you don't know what it is you can always post it in my Discord so uh great this is definitely working one thing that I'm not seeing is Albert Einstein's message here uh sorry Albert Einstein's image here so let's quickly go inside side of our components let's go inside of chat messages so where we map our messages and let's see what we missed why is this message uh not showing or perhaps we forgot to add it in our in our app folder API chat chat ID right here maybe we forgot to pass it here so we're passing the user ID here but I think that we have to also create let's see I'm going to pause the video a bit just so I uh debug this and I'm gonna tell you exactly what I did oh well the issue is very simple so go back into chat messages go inside of this uh chat messages.map and instead of using message.source we need to use companion.source like this there we go now there it is and let's go ahead and let's ask him who are you for example let's see if it's capable of answering yes that he is indeed Albert Einstein so sometimes it can take some time sometimes it answers instantly uh there we go hey there excitedly I'm the genius behind the famous equation E equals m c squared my name is Albert Einstein and I'm a renowned physicist if you have any questions about the universe or the mysteries of existence I'm your guy there we go I think this is absolutely amazing so yeah now we can go ahead and you can create as many of these companions uh as you want and you can use my prompts here and you can try and create your own using that information so I have one for Jeff Bezos for Elon Musk so you can use a bunch of this text which I prepared for you so you can create as many companions as you want great now what we have to do is we have to implement stripe we have to deploy and we are officially done with the project great great amazing job so far so in order to add subscriptions to our project we first have to install stripe and then we have to set up some environment keys so let's go ahead and let's open our DOT environment file and let's prepare an API key stripe underscore API underscore key like this and while we are here let's go inside of our terminal I'm going to shut down the application and I'm going to write npm install stripe like this so let's wait a second for this to install and you don't have to run the project immediately because we also have to add a schema but before we do that let's go ahead and let's obtain this stripe API key so I want you to go to stripe.com and I want you to log in into your dashboard so right here I have a bunch of stores already but if you have no stores you're probably gonna see this model right here so either will you see this the moment you log in into stripe if this is your first time or if you already have some go ahead and click on this current store at the top and click on new account here let's create a new one called AI companion like this and let's click create an account like this and in a second we're going to be redirected to this new dashboard and here you can click on API keys for developers or if you don't have that you can just click on the developers tab in the upper right corner so let's go ahead I'm going to click here API keys for developers right here so that is actually in this tab developers right here and then API keys and what I want you to do is I want you to click reveal secret test key like this and copy that key and that is what we need so let's go ahead and let's paste that in here like this great now that we have that let's go ahead and let's start adding our schema Prisma so go ahead and close everything and go inside of Prisma schema.prisma right here go all the way to the bottom and let's go ahead and let's create a model user subscription like this it's going to have an ID which is going to be a type of string it's going to be ID and default value uuid like this we're also going to have user ID which comes from clerk that's just a type of string and it is unique like this we're also going to have a stripe customer ID which is also a type of string but it is optional like this it's also unique and we're gonna map it to a different name in the database stripe underscore customer underscore ID like this so this is how we're gonna access it using Prisma so in Pascal case but in the database I want to map it to this underscore uh convention great so just make sure that stripe customer ID and the map is the same word so stripe and customer and ID like this but all lowercase and with underscores great you can go ahead and copy this one because the next one is going to be very similar this is going to be stripe subscription ID meaning that we also have to change this map to stripe underscore subscription ID like this and it's also an optional string which is unique so I'm just going to move this lines so they all align like this great you can go ahead and copy this one as well and this is going to be stripe price ID like this and you can already know that we have to change this to price ID as well and it's also an optional string and let's go ahead and let's copy this one this one is going to be stripe current period and like this so we're going to use this uh to check whether stripe subscription has expired or not and let's go ahead and let's map that to stripe underscore current under screen period underscore end like this so just go ahead and confirm that you didn't do any typos so current and period and and like this great but stripe current period end is not going to be a string instead it's going to be a date time like this so an optional date time great so now that we have the user subscription let's go ahead and let's do what we always have to do so save your schema.prisma and go ahead and run MPX Prisma the generate so now it's in our node modules and we can code with it and now npx Prisma DB push so it's up to date with the database so let's just wait a second and there we go great and we don't have to start the project yet first thing I'm gonna do is I'm going to create a lib for stripe so go inside of your lib folder and create a new file stripe.ts like this wait let's go inside and let's import Stripe from stripe like this and let's go ahead and write export con stripe to be new stripe and let's use the process.environment.stripe underscore API underscore key like this and let's go ahead and let's pass in the API version to be uh 2022 11 15 like this and for the second one it's going to be typescript and the value is going to be true like this and let's just quickly fix this error by adding an exclamation point at the end like this and let's go ahead and Let's cross check this environment variable with our DOT environment file so stripe API key it seems to be correct stripe underscore API underscore key and just confirm that it's the same right here you can copy and paste from one another great now let's go ahead and let's create uh the usil which is going to check whether our subscription has expired does it even exist and stuff like that so let's go ahead inside of our lib folder and create a new file called subscription.ts like this let's go ahead and let's import out from clerk next JS like this and let's import Prisma DB from dot slash Prisma DB or you can change it to add slash uh not Libs but lib Prisma DB like this great let's go ahead and let's write a hard-coded constant uh which is going to represent a whole day but in value of milliseconds so const day in milliseconds is equal to 86 underscore 400 underscore zero zero zero like this and now let's go ahead and let's write our function to check if the subscription is valid so export const check subscription isn't asynchronous for Arrow function first let's go ahead and let's extract the user ID because we can only fetch the subscription by user ID so we're gonna get that from alph like this if there is no user ID that means subscription is not valid so let's return false immediately because we cannot even check if it's valid so we're going to assume that it's not great now let's go ahead and let's attempt to fetch the user subscription so we can use this current period end date to confirm that it has not expired so const user subscription is equal to await Prisma DB dot usersubscription dot find unique and let's go ahead and let's open aware passing the user ID to the user ID like this and let's select stripe subscription uh period end to be true so stripe customer ID true stripe price ID true and stripe subscription ID true like this great and now let's go ahead and let's check if we weren't able to fetch the user subscription so if this resulted into null so if there is no user subscription that means user has never subscribed and we return false and now if user subscription already exists that's still not enough for us to confirm that user has an active subscription because we have to confirm that it's actually valid and not expired so let's go ahead and let's write const is valid like this and I'm going to write user subscription dot stripe priceid and user subscription dot stripe current period and question mark dot get time like this and put an exclamation point at the end to fix the typescript error so we're gonna check uh if the current period End plus one day in milliseconds so we're going to give one day grace period so if the entire period End plus one extra day has expired from uh comparison to today's date in that case that means that it's valid so return and let's just go ahead and turn this is valid into a Boolean so we can do that by using double exclamation point is valid like this so don't accidentally put one exclamation point that is just going to reverse the value but this does not reverse the value this just ensures that it's always a Boolean so either true or false great so now that we have that we can go ahead and create some routes for stripe so let's go ahead and let's go inside of our app folder inside of API and go ahead and create a new folder called stripe and inside create a new file route dot TS like this great and inside of here let's go ahead and let's import out and current user from add clerk nexus.js like this let's go ahead and let's import next response from next server let's import Prisma DB from add slash lib slash Prisma DB let's import Stripe from add slash Libs stripe and before we continue let's go ahead and let's create one util that we need and I'm going to explain uh how we're going to use it later so we need to create a util that is going to create an absolute URL that's because the stripe is going to work in a form of web hooks in and in form of redirecting us away from our website onto an official stripe URL so in order for stripe to know where to go back after a successful checkout or a failed checkout we need to give it a complete URL so we need to do something like HTTP localhost 3000 slash settings or when it later becomes your website is going to be www.yourwebsite.com settings right so we cannot just pass in slash settings in this route which we are gonna uh where we're going to use the stripe udil As and we're going to have to pass this URL somewhere so we need to create a util that is going to create an absolute URL so let's go ahead and let's go inside of our lead folder again and go ahead and go inside utils.ds so here where we have the CN function and go ahead and just write a simple export function absolute URL which accepts the path which is a string and let's go ahead and just return open backticks process.environment dot next underscore public underscore app underscore URL like that and let's go ahead and attach the path which we are going to pass in this function later on so you already know we have to add this to our environment file so let's go ahead and copy this go inside dot environment and just paste it here and in here you have to paste your website URL so for us that's going to be http so not https make sure it's HTTP slash localhost 3000 like this without the slash at the end so make sure you don't accidentally add the slash so just like this so now when we use this usual absolute path we're going to use it like absolute URL and we're going to pass in settings for example and that is going to result in localhost 3000 slash settings so that's why we needed this absolute URL actually it's gonna result in HTTP localhost settings so this is what we need to pass the stripe that's why we need this util and later when we deploy we're of course going to change this URL to an actual URL of wherever we deployed so let's go back now inside of our stripe route right here and now let's go ahead and import that so import absolute URL from utils and let's go ahead and immediately generate the settings URL where we are going to redirect to regardless if the checkup was successful or not the subconscious the settings URL is going to be absolute URL slash settings like this great now let's go ahead and let's write export asynchronous function get like this and let's go ahead and let's open a try and cache block and as always let's just resolve the cache first so we have an error here and let's come the log and let's go ahead and let's write stripe underscore get and let's pass in the error so we know something went wrong here and let's return new next response internal error with a status of 500 like this great and now we can go inside of the try block and first things first let's extract the user ID from out like this and let's get the entire user from away current user uh like this so we imported both of those from clerk here at the top great now let's check if there is no user ID or if there is no user in that case return new next response unauthorized and give it a status of 401 like this great but if we do let's go ahead and let's attempt to find a subscription so this slash API stripe route is gonna do two things if the user is not subscribed it's gonna redirect the user to the checkout page but if the user has an active subscription it's going to redirect them to a billing page where they can cancel the subscription or look when it renews and their credit card and a bunch of other useful information so that's where we first have to check whether user has an active description so let's go ahead and write const user subscription is equal to 08 Prisma DB dot usersubscription dot find unique where user ID so as simple as that so if there is a user subscription that means user has already done this but it also but it clicked on the button again meaning we want to direct redirect the user to the billing page instead of the checkout page so we're going to do that first so if there is a user subscription and if there is a user subscription dot stripe customer ID in that case we're going to create a stripe session which goes to the billing portal so cons stripe session is equal await stripe dot billingportal.sessions dot create and let's go ahead and let's pass in the options of customer to be user subscription dot stripe customer whoops stripe customer ID like this so it knows to which customer is this billing portal being created for and return URL is gonna be finally our settings URL so after user updates his billing settings it's gonna get redirected back to slash settings which is a page we don't have yet but we're gonna create it and we have to do return new next response let's go ahead and let's write json.stringify like this and let's go ahead and pass in the URL stripe session dot URL like this great so that's it for the billing portal now we have to create the other case and that's if it's user's first time subscribing so we have to create a checkout session so let's go ahead and let's write cons stripe session again to be await stripe but this time dot checkout so here we have the billing portal because user is trying to edit its information but here it's user's first time subscribing so we go ahead and write swipe.checkout.sessions dot create like this and let's go ahead and add the success URL to be settings URL and let's also do the same thing for cancel URL so it doesn't matter to us we're always going to redirect the user back to our settings so let's go ahead and let's add the payment underscore whoops underscore method uh underscore types which is an array and just pass in the card or you can of course go ahead and pass whatever you want cash app uh clarna you you can see you have PayPal even so you can go ahead and add a bunch of those stuff but I'm just gonna add add card for now and let's use the mode to be subscription because this is a subscription like this and let's go ahead and add the billing address collection to be Auto like this so this is going to ask the user for some more information you can remove this field if you don't need it you can always play around with the settings but uh for now I recommend you do exactly what I do and then when you finish the tutorial you can play around and modify it to your use case now let's go ahead and let's add the customer underscore email and we're going to pre-fill that email using this current user which is logged in so we're going to use user.email addresses which is an array and pick the first one and just write that email address like this and now let's go ahead and let's create options which item can which user can purchase in this checkout so for us that's just going to be one item and it's going to be a nine dollar monthly subscription so line items let's open an array and again later when you finish the tutorial you can add more tiers inside so I'm just gonna have one subscription tier so one object and we have to add the price data for this object let's define the currency so I'm going to choose US Dollars you can go ahead and choose something else of course uh product underscore data let's go ahead and fill that so the name of this product is going to be companion Pro like this and description for this product is going to be create custom AI companions because that's what we offer so it doesn't really matter of course for your product let's go ahead and let's define the price so we Define the price using the unit amount like this and you don't write 9.99 instead you write nine nine nine like this so this is going to be equal to 9.99 meaning ten dollars practically and to make sure that it's actually a subscription let's add the recurring option interval months like this so this is a monthly subscription great so now that we have that let's go ahead outside of this price data and let's just add quantity to be one because it's a subscription it's not an item that can be added multiple times to the cards so it's only one item that we have great and one important thing we have to add outside of this line items so go to the end of the array of the Align items we have to add the metadata so metadata is going to have the user ID the currently logged in user ID this is very important because uh just by user finishing just by the fact that user is going to check out successfully does not mean that it's automatically going to be inside of our database it's going to be inside of stripe but then stripe is going to fire back a web hook to our API and we are going to read the metadata user ID in order to know okay so this thing that stripe just fired back at our API is referring to this user that's why this is very important otherwise your user will subscribe your stripe will return the web hook and you will have no idea who actually subscribed and for whom you have to create the user subscription uh database column so that's where this metadata is very important great now that we have that all we have to do is write return new next response Json does stringify like this and an object URL stripe session dot URL like this great so now that we have that let's go ahead and let's create our web hook API call so in order to add our stripe web cook we first have to add another environment variable so I'm going to go ahead I'm gonna close everything and I'm going inside of my DOT environment while right here and let's go ahead and just below this stripe API key add stripe underscore webhook underscore secret like this great so make sure you have this and now let's find a way to fill that so go back in your stripe API right here sorry your stripe dashboard and you have to go inside of your web hooks right here and later we're gonna use add an endpoint but now since we are testing in a local environment we need to click test in a local environment like this first thing you have to do is you have to have the CLI for Stripes so you can run these commands in your terminal otherwise a stripe is not going to be found so click on download the CLI if you don't have it and just go ahead and follow the instructions for your device if you are on Windows follow this if you're on Linux follow this Mac OS I'm using Homebrew this I think is the simplest way but Homebrew is available on Mac I'm not sure about other os's so just make sure you set up your stripe CLI and once you have it let's go step by step and follow these instructions so I'm going to go ahead and I'm going to open my terminal so I don't have my project project running and let's do the first one so stripe login you can just click copy and paste it here so the command is stripe login like this and you can see that I have a pairing code right here and you see I have a URL right here so let's go ahead and let's click here and click open and that's going to open uh this the connection URL in your browser and you can see this code here so smart Share cure holy is the exact one I have in my terminal so this is just to confirm to you that you're connecting to your terminal and not something else and make sure your AI companion is selected here which we created in the beginning of the stripe dashboard and click allow access like this there we go you can now close this window and in a few seconds you're gonna see a success message here there we go so completed logged in on Antonio Macbook so that's going to say it's going to say something similar to you so this is completed great now we have to do this so let's go ahead and let's copy this and let's paste that command here and we have to modify it a bit so we're we are now mocking the web hook so stripe listened forward to and we have to modify this URL of course because we are not working on this port and our API is not going to look like this so go ahead and write localhost 3000 slash API slash web hook like this and press enter like that and now you finally have your web hook signing secret so copy this starting with w h s e c and ending with a zero in my case so this bold the text go ahead and copy the entire text and that is your environment variables for the stripe webhook secret so just make sure there are no trailing spaces at the end or at the beginning uh right here and save this file great and now we can close the terminal make sure you are running this uh stripe listened to as you can see it's completed and we don't have to do the step three great so now that that is done we can safely create our web hook route but just always make sure that you have this running so this command the stripe listen forward to you have to have this running while you're developing on localhost if you want to test your subscriptions so make sure that this is running okay now let's go back inside of our app folder inside of API right here and let's create a new folder called webhook so new folder web hook like this not web hooks my apologies but webhook One Singular and inside route.ds like this so this is going to result the slash API slash web hook and that is the exact thing which we are running in our terminal so logo host 3000 slash API slash web hook make sure that matches great now let's go ahead and let's import Stripe from the package stripe let's go ahead and let's import headers from next slash headers let's import next response from next slash server let's import Prisma DB from add slash lib Prisma DB and let's import the package sorry the util Stripe from s slash lib stripe like this now let's write export asynchronous function post let's pass in the request like this and let's write const body to be await request dot text and now let's get the signature from the headers so const signature is equal to headers execute the function dot get and now we're going to search for stripe Dash signature like this as string like this there we go and now using this signature and the webhook secret we can confirm that this is a valid request coming from stripe and not something else so let's write let event stripe dot event like this let's open a try and catch block so in the try we're gonna assign the event to be stripe Dot webhooks dot construct event like this let's pass in the body let's pass in the signature and let's passing process.environment dot stripe underscore webhook underscore secret like this there we go uh whoops like this and just make sure that you uh cross check this with your environment file so strike web hook secret let's go to the environment file stripe webwork secret okay it seems correct great and now let me just see what's going on with this error right here oh the error is in this type that I put it here so it should be lowercase string like this my apologies great so just make sure that this is a lowercase string not uh uppercase so string like this great now that we have that let's do the catch function here so catch error which is the type of any and let's just do return new next response let's open backticks web hook error and let's pass in error Dot message and a status of 400 like this and that's going to be it for our catch function so this uh successfully created our webhook event or it returns an error if the webhook event is incorrect great now let's get this session so con session is equal event dot data.object it has stripe.checkout dot session like this now this web Hook is going to handle two things so the same thing as in our stripe API route either the user is updating its payment details or the user is subscribing for the first time so we're gonna have to check both of those things let's go ahead and let's do the case if user is subscribing for the first time so that's going to be if event DOT type is identical to checkout.session dot completed like this in that case go ahead and write const subscription is await stripe dot subscriptions dot retrieve and pass in the session dot subscription as string like this there we go so now we have the subscription and now let's go ahead and let's check if there is a missing metadata so this is what I was talking about how important that metadata user ID is so if there is no session question mark dot metadata question mark dot user ID make sure you put the exclamation point here so we're checking if this doesn't exist so if that is missing in that case we have to return an error because we don't know to what user do we have to assign this new subscription that's why it's important to pass that metadata so return new next response user ID is required like this with a status of 400 like that great now let's go ahead and let's create the subscription in Prisma so await Prisma DB dot usersubscription dot create and pass in the data so user ID is going to be session question mark dot metadata question mark dot user ID like this stripe subscription ID is going to be subscription whoops subscription dot ID like this and then we're gonna have a stripe customer ID that's going to be subscription Dot customer as string after that we're going to have stripe price ID which is going to be subscription dot items the first one because we only have one sorry items.data the first item dot price dot ID like this and stripe current period and it's going to be new date like this and passing subscription dot current underscore period end times 1000 like this so you can fit in our database great so now we created a new subscription for that user and that's it for this case that we need what we need to do next is handle the case if user just updated uh their credentials or cancel the subscriptions or something like that so if event.type is invoice dot payment underscore succeeded in that case let's go ahead and let's retrieve the subscription again so const subscription is equal to 08 stripe dot subscriptions dot retrieve and passing session dot subscription as string like this great and all we have to do now is just update the existing user subscription with the new renewed information so I'll wait rismandb dot user subscription dot update like this where stripe subscription ID is subscription dot ID and let's pass in the data so data we're going to update is stripe price ID which is subscription DOT items.data first one dot price dot ID like that and stripe current period and new date and passing subscription dot current period and times 1000 like this great so now that that is done very important at the end you have to return new next response no and a status of 200 like this great so important thing here make sure that your inline event types are correct so I'm gonna go ahead and I'm gonna double check them so I copied it from my source code now and I pasted it here there we go this is correct so invoice dot payment underscore succeeded with two C's and two e's like that and the first one is checkout.session dot completed right here so I'm gonna copy from my source code again I'm gonna paste here to confirm it's all good okay so if you copied exactly as I did everything should work as expected and one thing we have to do is we have to remove uh clerk protection from this API route so let's go ahead and let's go inside of our middleware so find middleware.ts go inside of this export default middleware and add public routes array slash API slash web hook so we don't want to protect that route because stripe is gonna have to have access to that route and stripe is not a logged in user in our application so that's why it uses metadata to pass in the user ID which was logged in when they created the checkout session great so you fully completed the back end for stripe and now we have to go and do the front end great job so far so now let's create the UI for our stripe subscriptions so let's go ahead and before so keep this running so you have to have one terminal where you are running your web hook and now go ahead and open another terminal and before you run the project let's go ahead and let's install a couple of packages so first thing I want to install is npm install to stand so we're going to use this as a global State Management to open and close our models and now we have to add another chat CN component so let's go inside of chat cnui click on components and go ahead and find the dialog component once you found the dialog component go ahead and follow the instructions for installation so it's this Command right here I'm gonna go ahead and run this command now so npx shadesian Dash UI at latest add dialog and just press enter and confirm the installation in the terminal and now the dialog is installing once that is done go ahead and npm run Dev your project and remember keep your web hook command and so npm listen forward to localhost 3000 slash API webhook running great so I'm going to go and expand my screen I'm going to go back here and I'm going to refresh so I'm up to date uh with my recent uh package installations great first thing I'm gonna do I'm gonna close everything and I'm gonna go and Set uh hooks folder and create a new file called views Dash Pro Dash model dot DSX like this let's go ahead and let's import create from to stand I like this and let's go ahead and let's write interface use pro model store like this is open it's going to be a Boolean on open is going to be an arrow function like this and on close it's also going to be an arrow function like this great now let's go ahead and let's actually create the hook so export const use pro model we'll use this create which we imported from two stand let's give this create a type of use pro model store like that let's go ahead and Let's Open brackets Open brackets again extract the set prop and return an arrow function which is going to immediately return an object so we do that by opening parentheses and then an object inside like this and now we have to assign all of these values and what they do inside so it's open by default is a Boolean and it's going to be set to false on open is going to be a function which is going to modify the current state by changing the is open value to true so for that we're going to use this set prop right here so we call the set and the set will modify the object by setting is open to True like this and go ahead and copy this and just replace this one with on close which will do the same thing but instead of is open it will change it to false like this there we go so now we have the controls for our model set now let's go ahead and let's go inside of the components folder and let's create a new file pro Dash model dot ESX like this let's mark this as use client like that and let's go ahead and let's write export const pro model like this and let's go ahead and let's return our dialogue so go ahead and import dialogue not from Radix but from dot slash UI dialog like this and I'm gonna replace this to use slash components so I'm consistent there we go and now we have to assign some props to this dialog which are whether the dialog is currently opened and what to do after it's closed so for that we're going to use our hook const pro model use pro model from add slash hooks us pro model and then we can assign this open to be pro model dot is open and on open change is going to be promodel dot on close like this so that's how easy it is to control our model now and in any single component we want we can add this hook and we can just call pro model dot on open and that's going to open the model so that's how easy it is to control this model using two-stand Global store great now that we have this dialog let's go ahead and let's add a dialogue content which we also have to import from add slash component UI dialog and not from Radix like this let's add the dialog header also from add slash components UI dialog like this so I'm just going to collapse this Imports so you can see better okay and now that we have the dialog header let's go ahead and let's give this a class name of space Dash y-4 so a little space between each of the elements which are going to appear here and let's add the dialogue title also from add slash components UI dialog so I added that right here a great and this dialog title is going to have a class name of text Center and let's just write inside upgrade to Pro like this and before we continue any further let's go ahead and let's add this pro model to our layout so we can actually see what we are developing so wrap up this code so far and let's go inside of our app folder and inside of this layout right here so the main layout inside and just above the children but still inside of Team provider go ahead and add the pro model from add slash components slash pro model like this so I imported that right here and save this file great and now let's go ahead and let's see how we can open it so first thing we can do is we can just test out the hooks use pro model and change this default is open false to true for example and there we go look at this the model is now opened so that's what we want but we don't want to control it hard coded right here right so remove this back to false we want to use this on open function so close everything and go inside of components and find the navbar component let's go ahead and find this button which says upgrade and in here on that click we're going to open the model so for that let's go ahead this is marked as use client so that's important make sure that your navbar is marked as use client and let's just add the pro model hook so const pro model is equal to use Chrome model from add slash hooks use pro model this is the import and now we can just simply add an on click to this upgrade button to be pro model dot on open like this that's it let's go ahead and let's refresh this and let's test once I click on upgrade the pro model appears beautiful that's exactly what I wanted so keep your pro model open and let's close everything and let's go back inside of our components and let's develop this pro model uh further great so after the dialogue title let's add the dialog description from add slash components UI dialog so I added that right here make sure always confirm that there is no relics Imports while developing with chat cnui this dialog description a class name of tax Dash Center and space Dash Y dash 2 like this and inside go ahead and write create like this and then write a span custom Ai and then companions like this so now it says create custom AI Companions and I want to give a special color to this span so go in this pen and add a class name to it and write text sky-500 mx-1 and font Dash medium like this and now you can see how it's bluish color and it has space from both sides and it's a bit bold font great so that's exactly uh what we wanted and it actually doesn't need mx1 if you manually put spaces right but if you don't put spaces between these two then it needs uh then it needs that so if you want to do something like this for example you need to add a mx-1 say separated like this so choose whatever you want just make sure it looks okay great now let's go ahead and let's find the end of this dialog header and let's add this separator so import that from dot slash UI separator so like this dot slash UI separator and replace it with Slash components if you want to be consistent great now we have that separator and let's go ahead and let's open a div here and let's write class name blacks and justify Dash between like that let's go ahead and open a paragraph which is going to say nine dollars and then we're gonna open a span like this and you're gonna write dot 99 per month like this so it's going to look like this in the code but we're going to modify it a little bit so let's give this paragraph a class name of text to Excel and fond Dash medium like this and let's give this span a class name of text dash s and and font normal like this there we go so it looks a bit cooler now that we have that let's go ahead and find the end of this paragraph and let's add a button on the other side and make sure you import the button from dot slash UI button so I added that here and I'm going to replace it to components like this so button from slash components UI button and now that we have the button let's give it a text of subscribe like this and let's give this a variant of Premium like this it has that nice gradient color perfect what we have to do now is the actual functionality for this subscribe button so what we're gonna do is we're going to use axis to actually point to our stripe checkout page so let's go ahead and let's create our const on subscribe function right here is going to be an asynchronous error function like this so go ahead and open a try and catch block like this whoops like this and let's go ahead and let's add the toast so cons the structure toast from used toast from dot slash UI use toast and that is a hook which has to be executed and I'm going to replace this with Slash components like that and let's also add a loading state so const loading set loading new state from react and default value is false and I'm just going to move this to the top right here there we go okay now let's go ahead and let's add a finally block here which is just going to reset the loading to false again which means that in the try we're gonna Begin by setting the set loading to True like that and in catch all we're going to do is write a toast variant is going to be destructive and description is going to be something went wrong like this let's continue with our try block so in here we're going to write const response to be await axios which we have to import so make sure you import axios and I'm going to move it to the top with react like this so axios dot get slash API slash stripe so that is the API route which we created recently so it's in our API folder stripe route.ds so depending on whether the user is already subscribed or if it's their first time subscribing either the checkout session is going to open with our price data and our card payment and subscription and all of that information or the billing portal is going to open so since this is our first time we expect the stripe checkout to open so once we get that response we can go ahead and write window.location.href to be response.data.url like this so how do we know that we're going to get this URL well we just looked at this route so stripe route.ts inside of the API folder let's see what it returns so it Returns the URL object which is stripe subscription.url either the billing port or URL or the checkout URL so both of them do the same thing this is what we are fetching that URL great so now that we have that let's use this on subscribe on this button right here so on click unsubscribe and let's add the disabled prop to be loading like this great so I'm going to expand my screen now and I'm gonna refresh I'm gonna click upgrade here to trigger the model and I'm going to click subscribe here and let's see if we get any errors or if this is working so there we go look at this uh we now finally have our stripe subscription uh checkout page but now we have to confirm that our um that our web Hook is working so go ahead and go into your terminal right here and keep this page open and open another terminal where you're going to run the npx Prisma studio so go ahead and open that as well and I'm gonna go ahead and close this companion and you can see that we have the user subscription model right here so go ahead and click on the user subscription model and it should be empty for now and if this uh if everything is set up correctly we should get a new user subscription here and we should see success messages in this not this one but this so go ahead and look in your terminal where you have the stripe web hook running so where this uh webhook key is right and let's go ahead and let's fill this information now so I'm just entering some dummy data here so yeah use this information four two four two four two and just four twos like this so that is the success credit card if you enter anything else it's not gonna work so this is the test mode from stripe you can find more information about this test cards in stripe documentation so just Google stripe test cards and then you can you can test cases when it succeeds when it fails when there's insufficient funds and stuff like that but 4242 uh is the mock credit card for Success messages so we're gonna assume that user has a working credit card this doesn't matter just make sure it's in the future and let's go ahead and let's click subscribe and let's see if we made any mistakes perhaps we forgot something and this seems okay so we got a bunch of two hundreds one 500 and I think that that is this redirect maybe because we don't have the slash settings page if you look at the URL it redirected back to slash settings because that's what we called it in the uh API but it doesn't exist yet so this 404 is expected and let's now see Prisma studio uh if this created or not it did so it created a user subscription and that means that user is successfully subscribed and you can see when the subscription ends great so let's go ahead and I'm just gonna go ahead and go inside of my terminal where the application is running and let's see at this so we have uh an error here Prisma that users subscription dot update right here so we did get an error it seems so let me go ahead and just go through this a bit to see if this is something that is breaking the app or if something that has happened on accident so this certainly happened in the web hook I think so I'm just gonna go ahead through the code a bit and tell you if there's something that needs changing but so far it seems to be working fine all right so I did some Googling and I confirmed that the fact that this is 500 and failing it's actually okay for the first subscription that we are doing because this is the initial subscription and as you can see in the database we have all the correct information that we need it seems that this webhook is at the same time triggering the checkout session completed and then immediately the invoice payment succeeded and at that time we don't have this yet so we cannot update it but this is actually used for when customer changes their billing and updates uh their existing subscription so in that case that is going to work but in the initial case it's not working because this does not exist yet so yes I'm not exactly Satisfied by the error handling in this but I confirm that this is the code that I already used in some of my previous tutorials so it's certainly working and we're gonna confirm that now by actually using this user subscription model uh in some of the server components to confirm that it's actually working great so as long as you have this in your database in user subscription so the striped customer ID stripe subscription ID price and period current and everything should be absolutely fine and this other Hook is going to be working from now on because we do have this user subscription but in the initial code we don't have it so that's why it did not work using this find query right here great so now that we have this let's go ahead and let's actually use this lib that we created subscription so we're going to use this check subscription a lid now in some of the server components in order to actually show or hide some certain elements so actually let's go to localhost 3000 here and first thing we want to do is if we are subscribed we want to hide this upgrade button in the nav bar because we are already on Pro so let's go ahead and let's use this lib to hide that from the navigation bar so let's go ahead and let's close everything and let's go inside of our app folder inside of a root and find the layout.esx right here and let's go ahead and let's add const is pro to be await so in order to for this weight to work we have to turn this layout into an asynchronous arrow function so I'll wait and let's use check subscription from chat subscription like this and let's pass it to the nav bar so it's Pro is pro like this now let's go back inside of our nav bar so now bar is located in the components folder right here and let's create an interface for this so interface navbar props is going to take the is pro which is the type of Boolean so either true or false and let's go ahead and let's extract that here so it's Pro like this and let's give it the navbar props and now if it is pro so if we are successfully subscribed and the subscription is not expired let's go ahead and let's hide this so only if we are not pro so put an exclamation point is pro and then we're going to render this button right here like this so if I save this there we go so we are officially subscribed that's why we don't have that Pro button and if you were to delete this from the database then the button would show again so before we do anything further let's actually go ahead so we can close stripe now and this and let's actually delete this user subscription from our database so I'm going to delete this record in Prisma Studio like this great and now I'm going to refresh here and there we go so the upgrade button is back here again and that I want to leave it like that because I now want to protect my create route right here so when users click on create who are not subscribed I don't want them to be able to see this so I don't want users who are not on Pro to create custom companions so let's go ahead and let's do that now so we have to go back inside of our layout so go inside of the root folder and find the layout and the same way we passed is pro to the navbar we have to pass it to the sidebar as well so let's go ahead and let's pass this to the sidebar sidebar is also located in the components folder and let's create an interface sidebar Pro is pro and let's add Boolean like this there we go now let's go ahead and let's extract is pro like this and let's add sidebar Pro like that there we go and now we're going to use this is pro to confirm whether we are subscribed or not and we're going to use the combination of which routes we're protected with a pro to know which ones we have to check so we have it to do here check if Pro so let's go ahead and let's add an if Clause so if the route is protected by Pro and if we are not subscribed in that case make sure to return pro model so we don't have it let's go ahead and add it here so we need that hook const pro model is equal use pro model from hooks use pro model like this in that case we're going to do promodel dot on open like this so make sure you return this so it doesn't go here great let's go ahead and let's try that now so make sure you are not subscribed so delete everything in your user subscription and click create right here and there we go now it opens the model and one more thing we have to protect is the API because right now we just protected the UI so let's go ahead and let's protect the create API so we have to protect both the initial creation and the update of the companion so I'm going to close everything and I'm gonna go inside of my app folder and I'm going to go inside of the API companion and first let's do route.ts which has the post function here and let's go ahead and after this if which checks for all the necessary fields we have this to do check for subscription so let's remove it now and let's add const is pro to be await check subscription and you can import that from add slash lib subscription like this and now we're gonna go ahead and check if we are not pro go ahead and return new next response Pro subscription required like that and let's just pass in the error status 403 like this great and you can copy this and immediately go into companion ID so this is when we want to update one so that is for patch function and we have the to do check for subscription here as well so let's go ahead and paste that here and let's import check subscription from add slash lib subscription like this there we go so both our edit and create uh are now uh protected great great job so if you want to you can also add it for delete but I don't want to disallow users to delete their companions so if they are not on Pro I'm gonna allow them to delete even if their subscription expired I think that's fair great so now let's go ahead and let's test this out one more time so I'm gonna go inside of this create I'm going to click on subscribe right here oh but actually one more thing before we do that so I'm gonna go back oh yeah we have to do the settings page as well but before we do the settings page one thing that I don't think it's working I think that right now if I go on mobile sidebar so this pop-up oh it still works great okay no problem then so it still works so before we continue let's actually create this settings page so let's go ahead and do that now foreign let's go inside of our app folder so I'm going to just collapse everything and close everything go inside of the app folder here and go ahead inside of a root inside of routes and create a new folder called settings like this and inside create a new file page.tsx like this let's go ahead and let's export the settings page and let's just write a div saying hello settings like this and now if you go in your website and actually click on the settings right here there we go it says hello settings perfect so we can continue developing here let's go ahead and let's give this a class name of H dash full p-4 and space Dash y-2 like that let's give this an H3 element of settings like this and let's give this h3a class name of text large and font Dash medium like this let's go ahead and let's create a div with a class name backslash muted Dash foreground and textlash small like this and since this is a server component we can go ahead and call the is pro so const is pro await so we have to turn this asynchronous function and let's do a weight check subscription from ad lib subscription so this can only be run inside server components and let's use the dynamic uh option here so if we are pro let's write you are currently on a Pro Plan otherwise let's write you are currently on a free plan like this there we go so it says you're currently on a free plan because we don't have any subscriptions yet so now let's go ahead and let's go and create a subscription button so let's go inside of components and create a new file subscription Dash button.psx and let's go ahead and let's mark this is used client and export const subscription button like this and this subscription button is actually gonna accept the is pro status so let's create an interface is thrown to be Boolean like this and let's go ahead whoops I didn't give it a name so interface subscription button drops like this let's go ahead and let's extract is pro let's give it the default value of false and let's just add subscription button props like this great now let's go ahead and let's actually style this so it's going to be a button from dot slash UI button but I'm going to change this to slash components UI button like this let's go ahead and let's give it a size small a variant depending on whether we are pro or not so is Pro in that case we're going to use a default variant otherwise we're going to use the premium variant like this and let's give it uh well that's it for now and let's just write so if we are pro we're gonna write manage subscription otherwise we're gonna write upgrade like this and we're also going to dynamically render the icon so if we are not subscribed so exclamation point is pro go ahead and render the Sparkles uh from Lucid react I'm just going to separate the Imports like this and let's give this Sparkles a class name of h-4w-4 ml-2 and fill Dash white like this uh great so now that we have that let's go ahead and let's add this button just below this div so subscription uh button like that and go ahead and import it from add slash component subscription button and let's pass in the is pro is pro which we already have uh here and let's just see what we did wrong so did they do any mistakes so subscription button oh well I did a mistake I forgot to return this so we have to wrap return like this my apologies all right so we have that now and there we go you can see how now it says upgrade the same way it does in our navigation bar and now I want to do the same function on click here so let's go ahead in the subscription button and let's add const on click to Be an asynchronous Arrow function which is going to have a try catch whoops try catch and it's gonna have a finally block so let's go ahead and let's add some states here so const loading set loading use state from react is going to be false in the beginning and let's move this to the top let's go ahead and let's add the toast from use toast from dot slash UI use toast but I'm Gonna Change it to slash components UI use those like this and I think that's all we need for now so in this try let's do the set loading true in the final Loop let's do set loading false uh in the catch let's do toast variant destructive like this and description something went wrong like this and in the trial let's go ahead and let's do a const response to be await axias so make sure you import axis from axios like this I'm going to move it to the top and we are going to call the very same route because our route can handle both cases whether the user want to upgrade whether they want to modify their subscription so all we have to do is access dot get slash API slash stripe like this and window.location.href to be response.data.url like this there we go perfect and let's just go ahead and let's add the disabled prop to be loading and let's just add the on click to be uh with on click here so on click like this there we go so now we can go ahead and test this in a different way uh let's go ahead and let's click upgrade here now and it should lead us to the checkout stripe I'm Gonna Fill In My Fake information here I'm going to click subscribe and I should get redirected back to the slash settings page and it should tell me that I'm currently on a Pro Plan so it works and if I click manage subscription again okay something went wrong and I think I know what this is so we have to enable the checkout portal sorry the the billing portal in test mode so if you look in your terminal and go where you're running the application and you're going to see this error right here which it says you can't create a portal session in test mode until your until you save your customer portal settings in test mode so you can either click on this link uh right here like that and now I am on customer portal or you can go ahead and find it yourself so let me see exactly where this is uh so I think I can go in developers Maybe or let's try and search customer portal there we go you can just search customer portal and go to settings building customer portal and you have to click activate test link like this there we go that's it that's all you have to do and now refresh this and try and clicking manage subscription again and this time I'm pretty confident that it should redirect us to the checkout portal so let's just see it seems to be loading for some time so I'm gonna pause the video to see if everything is okay okay there it is there we go so now it's not the checkout screen but it's the actual control of whether we cancel the plan whether we renew it whether we change the payment method great great job perfect so you have officially and you can see you can now click on create normally so it doesn't block you perfect and let's just try and see if this create is actually working so I'm going to go ahead and I'm going to add some other for example Stephen Hawking let's add that if you want these cool pictures you can visit hotpot.io hotspot.ai the link is in the description so let's use Stephen Hawking like this your red the uh radical physicist I think that's how you write it uh it's a scientist and I'm just not gonna film this for now so I'm just gonna go ahead uh actually you know what I'm gonna fill it with the proper information so I'm just gonna pause the video so you can go ahead in my GitHub coincide of companions folder and you can go ahead and find Steven right here just copy the Preamble and paste it as instructions like that and copy the seed chat right here so it knows how to behave like this great and click create your companion and since we are subscribed everything should be working Stephen Hawking right here there we go let's go ahead and click here hello I'm Stephen Hawking and let's say uh hello can you tell me more about black holes for example uh and let's see if it's still trained the way we expect it to be trained and there we go so it says excitedly black holes Cosmic monsters that consume all in their path yet their very existence show us the beauty of the universe balance so you can see that it truly thinks that it is Stephen Hawking great great job you finished the entire project this was a very complex project uh you have light and dark mode so we didn't do anything in light mode because it hurts my eyes but you can go ahead and check it uh perfect so you completed the entire app all that's left is to do a stripe sorry not stripe but all that's left is to do deployment so let's go ahead and let's do that so before we do the development sorry the deployment I just want to do one quick thing so I want you to go back inside of components and go inside of pro model here models are a common case of hydration errors and we did not see any during this project because in our layout we added suppressed hydration warnings and that's fine but I don't want you to have any hidden hydration warnings either so what I'm gonna do is I'm gonna use the trick for mounting which we already did somewhere let me just search so set mounted sorry is mounted let's see yeah so we used it in image upload right here so I'm gonna do the same thing and I'm gonna do it in pro model so const is mounted set is mounted use state to be pause and let's say you use effect here and let's just set these mounted to true so this is going to prevent from pro model to cause any hydration errors so basically what's going on here is that is mounted is going to be false on server side and then when we get the use effect that means that we are already on the client side and that we are safe to show this model so let's just import the use effect from react and let's just write if we are not mounted return null like this and I just want to test if this model is still working so in order to do that I'm gonna go ahead and just try another account so I'm going to sign out here and I'm going to log in with an account that I know doesn't have a subscription okay let's click upgrade okay the model is still working great that's exactly what we wanted uh and now let's go ahead and let's actually deploy this so before we do that we have to go ahead and go inside of our package.json and we have to add a post install script Prisma generate like this without a trailing comma so make sure you add that and I don't know what this error is let's just go ahead and refresh and I think it will go away it was probably because of the trailing comma so I'm refreshing now uh and let's just see all right I'm gonna go ahead and try and just shutting down the application and trying again sometimes that's uh the best fix so I'm gonna close everything and run Dev again and I'm going to refresh my Local Host while we're doing this uh I want you to go ahead and create a repository on GitHub so there we go the error is gone and let's go inside of GitHub and let's go ahead and let's click uh new repository right here let's go ahead and let's name it AI Dash companion I'm gonna make mine private create a repository like this and since this already exists we're going to use this second option so just copy all of this but before you paste it let's go ahead and let's commit these changes so go inside of your terminal and you can now shut down all of this terminals so you can even shut down the web hook it doesn't matter just leave one terminal open and let's go ahead and let's add git add git commit vinyl like this and let's go ahead and let's paste this which we copied and paste it right here there we go so now it's up to date and if you go ahead and refresh this you should now see the entire project here great now let's go ahead and let's go inside of our reversal and let's click add new project let's go ahead and let's use this AI companion and click import everything can stay the same but we do have to modify the environment variables so for that you can go ahead and go into your dot environment file copy everything and paste it inside and there we go all variables are going to be assigned right here like this and of course we're going to have to change the next public URL and we're gonna have to change the stripe web hook secret but we cannot do that before we get the actual name of our URL so let's click deploy I'm gonna leave it here I'm gonna pause and if I get any errors I'm gonna go ahead and fix them with you so I'm just gonna pause for now and I'll unpose when it's ready okay so I have an error here so there's actually two errors going on so this is the first one but this doesn't affect the build you can see that it continued after that but I do have this error for the missing type so let's go ahead and let's fix that I think you know exactly where this is so let's go inside of our app inside of a root and the layout right here and I think that we have to do that in this nav bar our mobile sidebar you can see this is the error so our sidebar doesn't have the is pro so let's use this is pro which we are passing in the navbar component let's go ahead and let's pass it the sidebar component so mobile sidebar let's go ahead and let's add it here let's add the type for it so is pro is a Boolean like this and let's pass the is Pro to the sidebar as well so this fixes the mobile sidebar great and all you have to do is go inside of your terminal again and run git add and get commit fix like this and git push after that has been done uh the deployment has automatically restarted now and you can see that by going in your there we go so just click on your name at the top and you can see that it automatically started on AI companion right here it has this one so I'm just gonna go ahead and click here and I'm going to observe it and I'm gonna unpause if there's any more errors all right so my build seems to have succeeded and what I have to do now is I have to use this URL that we have right here so you can see my URL uh is AI Dash companion Dash flex.versal.app so let's go ahead and let's copy this URL right here or you can click visit on your website and then copy this just use this big visit button so let's see actually this is the singular version so I don't want to use that I want to find the actual domain which is this one so for that you can always go right here and go click on AI companion and click visit here and then you're gonna get in the URL the actual URL right here so go ahead and go inside of the URL and copy this right here including the https and now let's go ahead and let's go into stripe go inside and click developers like there go inside the web Hooks and we already have a local listener but we have to add a hosted endpoint so just click add endpoint right here and pass in this endpoint URL slash API slash web hook like this so this is what it's supposed to look like your url slash API slash web hook like this and now click events on your account and click select events and let's see which events we are listening to so open the web hook so first one is checkout.session that's completed so let's find the checkout and let's mark checkout.session.completed right here and we can close the checkout and the other one we have is invoice.payment succeeded so let's go ahead and find the invoice and let's scroll down payment succeeded right here and click add events like that so you need to have checkout session completed and invoice payment succeeded and click add endpoint right here perfect and what you have to do now is click on this signing secret and click reveal and this is your new uh API uh a stripe API webhook so go back inside of reversal go inside of your project here and click on settings go ahead and scroll so you might have a sidebar and find the environment variables and go ahead and find stripe webhook secret variable so that's this one stripe web cook secret go ahead and click edit and replace it with the new signing secret which we just obtained from here perfect so paste it here and click save and one more thing we have to change is this next public app URL so go ahead and obtain that URL so it's this one not without the API right so just https AI companion or whatever your name is dot versal.app without the backslash so don't include this only this part like that and let's add that here as well so without the backslash and click save like that now that that is done go back inside of your deployments like this choose the one uh the latest one and click redeploy it like this and click redeploy again I'm gonna pause the video and we're going to test if stripe is now working on production all right looks like the uh production is working let's go ahead and let's visit the application now now I'm gonna go ahead and I'm gonna log in with my third account which I know does not have a API subscription and I'm gonna close everything here but I'm Gonna Keep My stripe webhook open to see if we set that up correctly I'm going to click upgrade here I'm going to click subscribe okay let's see using my dummy information here I'm going to click subscribe and let's see if this is going to catch the event right here so slash API slash web hook let's see I'm gonna refresh just in case there we go so two successful events you can see that this time payment succeeded uh did not fail so that's a good thing it looks like that was just a one-off event which happened great and you can see that I'm currently on a Pro Plan in production so it's officially working your app is production ready one thing I want to remind you of is that if you decide to reset your entire database remember you have to see the categories so we have this script which sees the initial categories so if you ever uh remove your entire database make sure that you run the command this command node scripts seed.ds don't run it again because it's just gonna add duplicate categories but if you decide to reset your entire database which is a common thing people do when uh going to production so I'm actually going to show you how to do that so you can do npx Prisma General sorry NP experience ma migrate reset like this so this is going to clean the entire database so you have to confirm that if you want to right so all data is now lost right we have no added companions no messages and no categories so if I go ahead and after you delete this make sure you do MTX Prisma generate again and make sure you do ntx Prisma DB push so you have to do this after you reset your entire database so you don't have to do this at all I'm just demonstrating what you have to do if you want a clean slate so now if I refresh my production so I'm no longer on localhost you can see how this looks so no Companions and no categories and that means that you also cannot create anything because there are no categories so what you have to do is you have to run that script node scripts cd.ds like this there we go and now if I go ahead and refresh here there we go and you can also see how I don't have a subscription because that was deleted as well and that we just see that subscript uh categories now and now you can go ahead and subscribe and successfully create your companion there we go you did an amazing amazing job this is a very complicated project a lot of new technologies a lot of Advanced Technologies uh if you like the video remember to leave a like share and subscribe thank you so much for watching and see you in the next tutorial
Info
Channel: Code With Antonio
Views: 169,170
Rating: undefined out of 5
Keywords:
Id: PjYWpd7xkaM
Channel Id: undefined
Length: 395min 20sec (23720 seconds)
Published: Mon Jul 31 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.