Build Your Own AI Chatbot (Next.js 14, ChatGPT, Vector Embeddings, Vercel AI, Shadcn UI, TypeScript)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
wers de my friends in this tutorial we will learn how to build our very own AI chatbot using the chat gbt API vector embeddings and the Vel AI SDK believe me these are the most important Technologies you have to learn right now almost every new app that gets built now has some form of AI in it often using the cat gbt API it's the most important skill to know in the future I myself already work as a freelancer with this exact Tech deck and I make good money with it and I have even more projects in the pipeline big companies all add some kind of AI features to their existing apps and it's omnipresent right now if you want to stay relevant as a developer this tutorial will teach you the tools necessary for this this is the same text stack I'm using myself to build AI apps okay so we will use nextjs to build a no taking app with AI integration the no taking functionality is pretty simple we have a list of notes Here we can update these notes we can add a new note with a title and an optional content we can delete notes and restore these notes in mongodb but you can use any database doesn't matter we also added du theme just because it looks cool but the most important feature here is this AI chat box this chat box works similar to cat gbt but the difference is that this AI knows about our nodes so it can answer questions based on these notes for example I have this one note here where my passwords are in and I can ask the chatbot what's my wifi password again then it loads for a moment and then it tells me my password which it found in this node and since this is a large language model it can do more than just search it can also summarize stuff for me or do other tasks for example let's say say I have this watch list here in this other Noe and I want to get a random movie out of this list then I can ask the please give me a random movie to watch from my watch list and the AI is smart enough to do these abstract tasks so let's try this out and as you can see we also Implement response streaming where we don't have to wait until all the text of the AI has finished loading instead it gets streamed in letter by letter just like it works in the chat gbt web interface and it gave me a random movie from my list John Rick for we can also ask followup questions for example give me another one and the bot is smart enough to understand that this new sentence is still related to our last question so let's try this out again we get streaming and it gives me another movie from my list now the quick cool thing about this app this will work just as well if we had thousands of nodes it doesn't matter if we have five nodes or 5,000 the bot will always find the relevant information in all our nodes for this we will use a technique called vector embeddings vector embeddings are basically what give our AI long-term memory because making a simple request to the jet gbt API is not difficult we could just send our six notes to it and tell it to work with it it gets more difficult once we have a lot of information because we can't send an indefinite amount of information to Jet gbt there is a certain limit of characters we can send to it and also the more characters we send to it the more we have to pay so you don't want to send like a 100 different notes to the API you only want to send over a few that are relevant to the current question and again this is where Vector embeddings come into play if you don't know what Vector embeddings means don't worry you will learn this in this tutorial and we will store these Vector embeddings in a database called pine cone which you might have already heard of but if you haven't don't worry we will use nextjs 14 here the latest version but these Concepts and the underlying techniques of creating vector embeddings and making the chat gbt requests works the same independently of the framework no matter if you build a website in react or with some other technology or if you are building a mobile app creating the vector embeddings and making the chat GPT requests are always works the same just the code is a little bit different you don't need any AI pre- knowledge to follow along in this tutorial but it's good if you have worked with nextjs before if you haven't you can watch my nextjs 13 beginner tutorial here on YouTube I will put a link to it into the top right corner of this video this beginner tutorial was built with nextjs 13 and not 14 but they haven't been any API changes in nexj S14 just under the H optimizations basically so it's still just as relevant for next js14 you can watch this beginner tutorial first it's short and efficient and then you will know everything to get started with nextjs and you can come back to this tutorial here for the UI we will use Tailwind CSS and a component Library called chaty Nui which you might have already heard of it's very popular at the moment and of course the layout is also fully responsive so this grid here changes everything moves out of place just as a should be for authentication because we can log in and out into our user accounts we will use clock which is an authentication provider and it's really the fastest way to set up authentication with Advanced session management features so without any further set up we get this user session management popup right out of the box where we can see the different devices we are locked in with our connected social accounts we can upload a profile picture to our user account they have two Factor authentication and all this advanced stuff this will help us set up authentication quickly so we can focus on the important part of this tutorial and just as a side note I also made this logo here with AI I created this with a free tool called Microsoft designer I actually created a YouTube short which is only one minute long where I explain how to create such a logo in 1 minute takes 1 minute to create this logo and it's completely yours it's copyright free because it's generated by AI if you want to learn how to do this yourself just type into YouTube search coding and flow Microsoft designer or coding and flow AI logo and you will find this very short video so check this out as well and then let's get started with this amazing AI app happy coding all right as usual I assume that you have no JS installed and vs code set up if you haven't again watch my nextjs beginner tutorial where I show you how to do this and as usual I will give you two options you can either set up the project manually yourself and install all the dependencies yourself or you can take my starting code from GitHub which I will put into the description under the video in this starting code all the packages are already installed the benefit is that you will have the exact same version numbers as I'm using and this project doesn't break in the future if you set up the project yourself then you will have the latest versions installed they might be a bit different than what we are using in this tutorial if you're very Brave set up the project yourself but if you are a beginner it might be better to use my starting code on GitHub so again the link to the starting code is in the description you can download it from there from GitHub then just open the project and run npm install in the command line to install all the packages and any graphics and images that we use in this project are also already contained in the starting code together with all the packages if you download the starting code then you can skip the manual project setup that we will do now to set up a nextjs project manually we go to the folder where we want to put it and then we want to open the command line here on Windows we do this by holding shift down and right clicking and then we open the powers shell or the command line I'm not sure how it works on Linux and Mac I assume it works similar you might have to Google this but yeah we need a command line that points to this folder where we want to create this project and then we type npx create minus next minus app at latest this creates a new nextjs app with the latest version we run this with enter when this text pops up we confirm with a Y and again enter and then we have to give our project a name I'm going to call this nextjs AI node app the name doesn't matter it's up to you then then we press enter would you like to use typescript yes would you like to use eslint yes as well TN CSS to we also want to use the srz directory and the app router so we confirm all of them with yes would you like to customize the default import alas this time we select no but again you only have to do this if you didn't download my starting project the starting project already has all of this set up and then we wait until this has finished setting up the project okay when it's done we can close the command line and then we want to open this project so nextjs a node app here it is we open it in the S code and then we need to install some packages for this we open the terminal if you don't know how to open it you can click here on view and with this button you can open a new terminal or with the shortcut next to it now we install the packages that we need again in the starting code they are already set up if you downloaded the starting code then just run npm install without anything else added to it this will install all the packages inside the package.json if you set up the project manually we have to Define what new packages we want to install so after npm install we type in all of them and we have to make sure that we don't enter any typos because they have very specific names the first two are open AI S one word are lowercase and AI which is another word the open AI package is provided by chat gbt this is how we interact Rift chat GB from our JavaScript code and the second package AI is the Vel AI SDK this package was created by the creators of nextjs and they provide very useful utility functions to interact with chat gbt Implement chat functionality and most importantly implement this streaming where we get the response letter by letter as you have seen earlier this is all made easy for us with this brael AIS SDK that we will also use after a space we also install Prisma another space and then at Prisma SL client Prisma is the oi M that we will use here it's how we interact with our mongodb database again you can also use another database then mongodb Prisma works with a lot of them you can check out the Prisma documentation if you want to learn more next we install at Pine Cone minus database SL pine cone as I explained earlier pine cone is the database where we store our vector embeddings this is how we give our AI long-term memory and don't worry they all have a free tier so you don't have to pay anything to follow along building this project okay but we still need a few more packages the next is ADD clerk SL nextjs again we use clerk here and then add clerk SL themes for Dark theme support then we also need next minus themes which helps us implement the Dark theme into our app and we are almost done we also use prier which helps us format our code this is especially useful with Tailwind CSS because it automatically orders the class names for us which makes it much less messy and just in general Pria is an amazing formatting Library it's opinionated so we don't have to worry how to format our code if we should add semicolons or not trailing commas or not Pria does all of this for us I really like it makes working with typescript code much easier and together with prer we also need the es lint minus config minus prer so that the S works properly together with Pria and one more prer minus plugin minus Tailwind CSS this is responsible for ordering the Tailwind classes and I just want to repeat one more time if you downloaded installing code then you don't have to install all these packages they are already inside the package.json okay and this should be all so let's execute this with enter this will take a few seconds or maybe a few minutes if you have a slow connection okay after this is done we can close the command line for now now we have all these packages here in our package.json twin CSS was already set up when we created the nextjs project now it shouldn't matter if we install packages in the dependencies or death dependencies block because the unused production dependencies will be stripped away when we build the project anyway at least this is how it used to work and by default all the packages used to be inside the normal dependencies Block in a nextjs project I don't know why they separated them again and created the separate death dependencies block but it shouldn't matter if we put dependencies like the prettier plugin into def dependencies or the normal dependencies block because again unused dependencies should automatically be removed when the project is compiled so I'm just going to keep them here in the normal dependencies block then as I already showed you I also prepared a logo for this app which I'm going to copy over into this project I will also put a link to this logo under the video if you set up the project manually you can download it from there but if you downloaded the starting code the logo and the F icon will already be in there so for this logo I want to create an assets folder inside the srz folder here so we right click on srz new folder assets and then I just want to drag and drop this logo in here to copy it great nextjs projects also have this generic faff Icon by default but I created a fa icon with the same logo which is here so let's delete the existing one and then drag and drop drop this into the app folder it has to be placed inside the app folder if you want to create your own faf icons there are different free generators available you can easily find them in Google search you just upload an image here and it creates such a FFF icon. I file which you can pull into your project but again this is already contained in the starting code project okay and then we also want to make sure that our IDE is set up properly which is especially important when we work with Tailwind because we need autoc completion and class ordering and for this we need another extension installed in the IDE this one here twin CSS intelligence go ahead and install this extension if you haven't yet this gives us autocomplete in these class name strings which is really important you can't really work properly with Tailwind without this otherwise you have to figure out each class by yourself which sounds like a nightmare to me so just go ahead and install this and then we want to go into the project settings to set this up properly so here on Windows it's preferences settings and then in the search up here we type in files associations we want to get to this setting here and we want to add a new item where CSS files are associated with this tailn CSS string so you just have to type in these values here this makes sure that our Tailwind extension is loaded inside CSS files and we can use it there and then we want to change another setting editor quick suggestions there it is we want to turn on quick suggestions for Strings so that we get autocomplete for Tailwind classes inside these class name strings make sure to activate this then remember when we installed all our packages we installed this Pria tent plugin and this also requires one file that we have to set up this Pria config.js file where we have to put in this code here so let's go into our project and in the root folder even outside of srz right here we create a new file which we call Pria Doc config.js and and in here we put this code you can type this out manually this makes sure that automatic class order ring works and by default we have to execute prer from the command line but to execute this with a keyboard shortcut right in our IDE I recommend that you also install this official prier BS code extension and after you have installed this we go into our settings once again and you can set the default formatter to prier so that when you press the formatting shortcut which on Windows is shift alt F you can also search for this in a file where we have code we can press control shift p or command shift p on Mac and this format document shortcut is how we format the document and by setting the default format to prer this is what will be used when we press this shortcut so go ahead and set this to prettier and there's one more change we have to make we go into the es RZ Json file we want to change this string into an array so we surround this with square brackets and as the second value in here we put pretty as a string this makes sure that es lint works properly together with Pria I know that all the setup can be a bit annoying but it will really be worth it because it makes working with our project in the future much easier that's the prettier setup I also recommend that you install these two extensions if you haven't yet esland which just shows the code warnings and errors directly in your IDE so you don't have to run es on the command line and since we use Prisma as our om I also recommend that you install this official Prisma extension this will give you Auto completion and code formatting in your Prisma schemas which is very useful so go ahead and install this as well this shouldn't require any special setup you just have to install inst it and it should work right out of the box and when we have installed all these packages and set up our project we can close these tabs for now okay and now we are ready to start coding okay as I already mentioned we will use shedy Nui in our project which is a very popular Tailwind CSS component library for nextjs right now it's based on another Library called radx UI which are basically unstyled react component and chyn adds styling to them and it looks very clean and modern by default one reason why this component libr is so popular is that you don't install it as an npm package you don't just write npm install instead you basically copy paste these components into your project which means that you have all the code right in your project you can change it if you want you can update these components independently so you own all the code yourself and many people prefer this over having an npm package that you have to install because then you don't really have access to the underlying code you can't make changes as easily and updates often break things we can literally just copy paste this code into our project we don't have to install anything but they also have these installation steps again those are not packages that you install instead it just creates some kind of configuration file in your project which then automatically copy pastes the component code into your project so this is not the same as installing a package it's basically just a more convenient way of copy pasting we will use this command but again I have already added this to this starting project so if you downloaded the starting code then you can just lean back and watch me set this up it's already contained I already added all the shety N components the reason why I added it to the starting code is that just just like packages these components change over time and if you watch this tutorial sometime in the future I don't want your project to break because these components have different code now this is why I added them to the starting code already if you set up the project manually then you have to follow along so we need to copy this command here the npm command go back into our project open the command line and then run this after this has finished loading again we get asked some questions would you like to use typescript yes which style we use the default style they just look a bit different which color would you like to use as a base color this is up to you I'm going to stick with slate which is like a blackish color where is your Global CSS file here we have to be careful because we have this srz folder so this default is not correct here we have to type srz / app SL global. CSS this is where our Global CSS file is located and chat CN will make some changes to it would you like to use CSS variables for colors we select yes as well I will explain this in more detail later where is your Tailwind config located it's in the root folder right here so the default is correct configure the import areas for components we keep the default here as well configure import for utils we keep the default here as well are you using react zvo components yes we do right configuration to blah blah blah proceed we type in yes to confirm this and then shed CN sets up some files in this project okay let's take a look at the files this has changed so this chy and init command set up this components Json later when we Ed head the N components which we also do via the command line it will use this configuration to decide how to set up these components again we could also just copy paste the components but this way it makes it easier to configure them properly because it will be done for us automatically it also installed some new packages but again the shety end setup is already included in the starting code so you can skip the step there yeah those are some utility packages it uses under the hood this is an icon package and then it also made a change to the global CSS file it added some CSS color variables here which we will later use a primary color a secondary color and so on more on this later it also made changes to the Tailwind config now it created this tail config JS and we also have this TN config TS in our project we don't need a TS file anym so let's go ahead and delete this and all the tailn configuration is contained in this TN config JS file so here it basically wired up the color variables that we saw in the CSS file and some other configuration on the shedy N website which you can find under this URL you can also configure themes with different colors and then you can get the code from here and you can copy this into your project but we will use the default theme here and then it also created this utils file with the CN function here here this is used to combine different Hain classes dynamically we will see this later okay and then the last thing to do is to install the actual shyn components that we want to use let's just install them all at once so that we don't have to keep coming back to this site later down here in the shedy end documentation are the components again this is already included in the starting project because the code of these components might change in the future so when you use Chad CN you just install the components that you want to use in your project and then you can always choose between manual installation which is basically just copy pasting or using the CLI which will use the configuration file that we just set up so we're going to do this for all the components we will use in our project so we copy this first one for the button back into our command line and we execute npx chaty nuui at latest at button again this doesn't install a package it basically just copy pastes the code of the button component over into our project but we can change this code if we want we have full access to it okay and then we will need a few more so we also need the card so again we scroll down to the installation command copy the npm command execute this in the command line now we get this card component next we need a dialogue and you can always see a preview of this components here they look very clean and modern again copy run the command we also need the form component this makes working with forms easier and it uses react hook form under the hood we will learn how to use these components later again we install this with this command and this should also install some more packages which are react hook form zot and the hook form resolvers here okay just three more and then we are done we also need input which is this styled input field again copy npm run this the input field automat aut atically installs the label component as well so we actually only need two more and not three so the last one is the text area which is just a larger input field where we can change the size so copy the npm command run this and this should be all components we need okay and this is the exact state of the project that I will push into the starting code with all the packages and CN already installed and the image files included and going forward everything else we have to do manually and the next step is to set up our mongodb database and Prisma okay we will use mongodb Atlas for this project which is a cloud holed mongodb they also have a free tier that we can use so we don't have to pay anything but if you rather use a different database then this is fine as well it doesn't really matter for the big picture of this tutorial and we will also do our Vector embeddings later in a different database mongodb is just for the nodes themselves so you can search on Google for mongodb Atlas and follow the link create a free account I'm going to log in with the one I already have and here we want to create a new cluster I already have some clusters in here so I click up here and create a new project if you haven't created a project before then there's probably a button right in the middle of the screen just follow along and create a new project which we have to give a name again I'm going to call this nextjs AI Noe app then we click on next we don't have to change anything here click on create project and then we click on create a deployment here we select m0 which is the free tier the the provider and region doesn't really matter we can also keep the name as it is then we click on create we have to set up a username and a password I'm going to change the username to floran we can keep the autog generated password we need to copy this for later and then we create this user where would you like to connect from my local environment is correct we add the current IP address which is the IP address of our computer but it looks like it has already been added for us and then we click on finish and close and then go to overview so this can take a few seconds or even a few minutes until this cluster is set up and then we want to set up Prisma which is an OM so it's the library that we use to interact with our mongodb database more easily than using the native mongodb driver so on the Prisma homepage is this quick start guide which brings us to the add Prisma to an existing project page this is all on Prisma doio but you don't have to open this page because I show you all the steps and code that we have to execute we already installed Prisma in the beginning so now we have to execute npx Prisma in it in our project so let's open the terminal again npx Prisma in it which created two new files I think yeah it created the N file with this connection string and this Prisma schema so let's open the N file here is this placeholder connection string we need to replace this for our mongodb connection string which we can find in mongodb Atlas where we left off here when we click on connect and then on drivers we can scroll down and find this connection string here which we need to copy we replace the connection string in the N file the username I set up is already insided but we have to replace this password placeholder here with the angle brackets for the password we set up earlier and by default I think the database is called test or something like that to give the database an explicit name we go after the slash here before the question mark and then we can name our database for example note database or just database the name doesn't matter we save this file and by default the N file is not included in the git ignore file of a nextjs project only this n.l so we go at the bottom and add this to get ignore so that we don't push the end file to GitHub if we decide to publish this project on GitHub you don't need to put this on GitHub but if you want to you never want to push your credentials so make sure to addn to the git ignore file next we go into our schema. Prisma file which was created for us and we change the provider from postresql or however this is pronounced to be honest I don't know to mongod because we are using a mongodb database and this down here connects the connection string in the n file and you notice that I have code highlighting and auto completion in here which only works because I have this Prisma extension installed as I recommended to you earlier when we set up the project otherwise V escod will handle this like a plain text file which is not so nice in the schema we also put all our models that we want to work with and that we want to store in our database later in this project it's only a single model for our nodes so we create a model called node and again it's really nice to have Auto completion here and then we add a few Fields the note needs an idea of type string with an uppercase s space at idea which tells Prisma that this is the unique ID of this model we want to give it a default value so it's autogenerated so we type at default and in here we have this Auto function that we can call then after a space we run WR at map and in the string you write underscore idea because in mongod Deb IDs have this underscore in front of them but in our code we want to have just ID as the name so this Maps the real name of this field to the name that we use within our own code and the last value we have to add to this line is atdb doob idea this tells Prisma that this is not a simple string it's a mongodb object idea which has to follow a certain schema the other fields we add to this node model are simpler each node will need a title in form of a string and now when I press the formatting shortcut shift alt F on Windows it aligns the code properly which again is done for us by the Prisma extension then each note will have the content which is the text of the Noe we also make this a string but we add a question mark after it because this will be optional we can also create nodes that only have a title but each node also needs a user idea so we know to which user this node belongs and then we need two Tim stamps one is called created at of type date time and we set a default value to this Now function so the created at Tim stamp will be cre created automatically and then we need the updated ad Tim stamp as well again of type dat time and here we write add updated at which automatically Updates this Tim stamp whenever we make a change to this document so we don't have to handle this ourselves okay these are the fields we need in our code this model will be called node but it's convention that the collection in mongod Deb has a name that is lowercase and plural so again we can use this map annotation here but this time we have to write two ads because we want to map the whole collection name so add add map and here we write nodes in lowercase and plural so again our collection will be called nodes but the model that we work with in our code will have this singular name then again we can format our code and that's our whole schema for this app then we open the command line again and we execute npx Prisma generate which generates the Prisma client that we can use in our code which has this node model inside it so whenever you make changes to your schema you have to execute this Prisma generate command again otherwise this model will be missing on the Prisma client and then the Prisma documentation has these instructions on how to set up a Prisma client in the nextjs project this basically cre a Singleton which makes sure that we always use the same instance of the Prisma client which is necessary in development because when we save changes to our project the server is restarted and this creates a new client and if this creates too many clients this can cause a problem so we copy this code from the documentation but instead of copying this I recommend that you type this out by hand at least once so you better understand what each line does and we put this code into our lip folder shed ZN actually created this lip folder when we initialized it earlier in here we create a new file and we write DB SL Prisma dots this DB slash creates a new folder called DB for our database code and in here I paste this code and again you can pause the video now and type this out by hand which will help you better understand what exactly is happening in here but this basically just creates a Prisma client as a single ton and then exports it here so that we can use it throughout our project and with this Prisma and mongodb for this project are also set up and we can soon start creating notes updating them deleting them and so on now before we start adding nodes to our database we have to set up authentication because each node should be associated with a particular user right so it's time to set up clock for this we go to clock.com I will also put a link into the video description and here we create a free account and again they have a free tier available so we don't have to pay anything I'm going to log in with the account that I already have when we have created our account we create an application which we have to give a name and again I'm going to call this nextjs AI note app by default this has email and Google login enabled we can enable more social providers later and the cool thing is that clar also sends these emails for us so we don't have to sign up for a separate email service this is all handled for us so we don't have to change anything else and we click on create application we can still make all the changes in our dashboard here later on the next screen we get our keys which we can copy then we go into our project into the n file and we paste them in here let's go back into the clerk dashboard and here they have this continue and docs button which gives us some more instructions just make sure you have nextjs selected before you click this button so we already installed the dependency in the very beginning so we can skip this step we also set the environment Keys just that we pulled it into the normal do n file and not. n.l but this is fine I want to keep them in the same file the next step is to wrap our application into this clerk provider context provider this makes our session available throughout our whole app so back into our project into the layout. TSX file and if you don't know what the root layout is I explain this in my next JS 13 beginner tutorial and we want to wrap this whole application this whole HTML tag into a clerk provider but it looks like Auto Import doesn't work so we have to import it manually import curly bracers from at Clerk nextjs and in here we should find the clerk provider and we want to wrap our whole application into this clock provider which makes the login session available throughout our whole app okay great let's take a look into the instructions again the next step is to set up the middleware which protects our routes so we can copy this code here if you use the starting code then you want to make sure that your meure looks exactly as the one I'm going to use this shouldn't really change with future versions but if it does and you use the same package version as I do then the meter also has to to match no pun intended and we have to put this into a middleware file which we have to put directly into the srz folder not inside app but directly in srz and we have to name this file middle ware. TS this is a special file in nextjs that will be executed before we access any page so I'm going to copy paste this here so again either copy paste this from the clerk documentation or just type it by hand exactly the way as I have it here and this protects all routes by default but we want to allow one particular route and that's the front page we want to make the front page available even when you are not logged in so we can go into this off middleware function here and in here we have this public routes field where as the name implies we can set routes that don't require authentication in form of an array that contains a string to a slash so the homepage we can format this with shift alt F and then save it and this is actually enough to get authentication to work and to see that it works let's create another page that's not the homage so not our public route so we right click on the app folder create a new file we type noes slash again this creates a new folder and in here we put a page. TSA if you don't know what the page TSX is again you have to watch my nextjs 13 beginner tutorial there I explain how routing Works in nextjs okay and for now we just start with a very simple page we export a default function called notes page but the name of the function doesn't matter you can call this anything you want you can also just colored page for example and for now we simply return a div which will say here will be your notes just keep it simple for now we Sav this make sure the middleware and all the other files are saved as well open the command line run npm run def to run our nextjs project on Local Host 3000 then we open Local Host 3000 the front page is public it's not protected by clock Authentication but when we go to slash notes we should be redirected to a login screen perfect this is the default clerk lockin screen we can customize the design of this later but the URL of this is not great as you can see it has this weird croal Pony URL this URL is hosted directly by clerk they do this so that we can quickly set up authentication without having to set up our own pages but instead of using these hosted URLs we can also put this sign in window into our own URL which is better right so let's close this for now go back into our project we keep the development server running but we can close the terminal then we right click on the app folder create a new file and then we have to make sure the path is correct because clerk looks for a very specific path so in the app folder we create a new folder called sign minus in slash then we have to write two pairs of scrap brackets like this not one but two pairs in here we write dot dot dot and then sign minus in again this is a special so-called catch all route this is described in the nextjs documentation but we don't have to understand this right now this is just what clar expects to handle sign in and sign up later but in here we want to put a page. TS X so make sure the path is correct and from here we can export our sign in page which uses the same sign in component that you just saw a moment ago just that this page will be located on our own URL so it will be a very small component we export a default function called sign in page again the name of the component does not matter and here we return a diff which is the page itself and in here we put this sign in component which is an import from clerk nextjs this is the same sign in form that you just saw a moment ago just in form of a component that we can use in our own code and then we want to Center the sign in component on our screen so we go into the outer diff and add some Tailwind classes we make this a flex box with the flex class and since we set up the tent extension and the vs Cod settings earlier we get this nice Auto completion if you're not getting Auto completion then you might have to watch the beginning again and make sure you IDE is set up properly with h minus screen we make this page as large as the screen and then I want to Center this sign and form on the screen and even though it's a meme that centering something inside the diff is difficult it's actually quite simple we need a flex box with item Center and justify Center and this centers the sign in form on the screen we can also customize this sign in form remember the one we just saw had a blue color but we actually use a blackish color as our primary color so let's set us up as well in this sign in component we have this appearance prop and in between these curly braces we add another pair of curly braces because this is a configuration object in here we have the variables field colon another pair of curly bracers and in here we want to set the color primary you can do more customization but this is the only customization I want to do here we can set this to a hex string and we will use 0 F1 72a which is just this blackish color that we use in our components so I want to use this for the signin buttons as well okay that's the signin page but we also need a sign up page which will look almost identical so let's copy this code and create another route in the app folder this time it's called sign up again slash two pairs of square brackets dot dot dot sign minus up and in here we put another page. TSX here we can paste the code but instead of sign in we want to use Clerk's sign up component this is used to create a new account and let's rename the page to a sign up page as well okay make sure everything is saved then we have to go into ourn file again and add these four environment variables datail clerk where our new sign in and sign up URLs are located and where we want to be redirected to after we signed in we want to get redirected to the/ noes page so pause the video type out these variables make sure to spell them correctly let's save all the changes and then let's try this again so again I go to/ noes again we get redirected to the login page but now it's located under our own URL much better and it also has our own brand color if you want to remove this brand branding here then you will have to get a paid plan but I think it's not a big deal and it's really nice how well authentication just works when you use clerk as a provider yeah and we can switch between the sign up and signin page which are both located on our own URL let's try logging in with a Google account and if you set up Google authentication yourself then you also have to create a Google app in the Google Cloud console but in development clerk provides this Google app for you which really convenient because it makes it easy to get started with social authentication pretty much instantly so I'm going to log into our node app with one of our accounts and once I'm logged in I'm allowed to view the/ noes path so now I'm logged in we have created a new user in the process which we should see in our clerk dashboard yeah total users one here it is and we even get some confetti we can find the list of all the users here in this user menu option and in this dashboard we can do a lot of customization you can take your time and experiment and play around with this we can also easily activate additional social login providers here under user and authentication social connections and you basically just have to turn them on again in development this just works in production you will have to set up an app for each of these social providers on their own homepage so for example for Google login you need an app in the Google Cloud console Facebook also has a developer portal where you have to create an app but in development it's very quick and easy with clar and yeah as you can see there are a ton of different providers available just for testing purposes I'm going to enable Facebook and I don't know GitHub maybe now when we go back into our app they should appear on the login page now we don't have have a log out button yet but what we can do is we can just clear the cookie where the JWT token is content so click up here then on cookies and then we remove them reload the page and we should be redirected to the login screen so now we have Facebook and GitHub here as well but I want to log in with the existing Google account again so authentication works just like this we can do some more customization here for example we can upload a logo under this branding option so for example we can upload our logo right which is in the project AI node app public now it's actually the assets folder in srz the flow brain logo and for the icon as well and again this just works immediately I'm going to log out once again again by deleting the cookie reload the page and now we see our logo here it just makes this login screen a bit more fitting to our brand again you can explore this clar dashboard further there are many customization options you can customize what data you want to store for each user you can even enable multiactor Authentication and so on but for our project for now we are all set the last thing I want to do for this part of the tutorial is set the metadata of our website so we go into the root layout once again up here is the page title and the description with the default text I want to change this I want to set the title to flow brain which is the name I've given this app and I want to set the description which is shown for example when we post a link to our website on social media but again I explain this in more detail in my next JS beginner tutorial I want to set this to the intelligent no taking app so now when we go to our homepage again we see the title flow brain up here but I want to set a more specific title for the notes page so in the noes page. TSX file we export metadata here as well export con metadata of type metadata which is an import from next and in here I just want to overwrite the title again I want this to say flow brain but then a dash and then notes because this is the notes page okay after saving this the hom page and login page still says flow brain now let's actually also set a separate title for this sign in and sign up page makes sense right so we go into the signin page let's set the title here as well so again export const metadata of type metadata title column again flow brain minus sign in let's copy this save it go to the sign up page do the same here add the metadata import and just change this to sign up so that we have a proper title on all of these different pages so let's save this and now on the sign in page we see flow brain sign in on the front page we see just flow brain and after we logged in on the notes page we should see the notes title and there it is perfect and now we can start building our Different Page layouts next I want to set up a enough bar that we only show on the SL notes routes not on the homepage or anywhere else just everything under slash noes so let's go back into our project we go into our slash notes folder and I want to put the nabar component in here you can name this anything you want I'm going to col it nabar in Cel case like this and for now I just want to export default a simple function with the same name that returns for now just a diff that says nabar just so that we can place it in our layout and see that this works and as I said I only want to put this SF Bar onto any route below slash noes so not on the homepage and not on the sign in or sign up pages but slash noes and if we add any nested route under SL noes like slash noes SL detail or slash add node or whatever I want to have the n Bar there as well so we can do this by adding a layout file into the notes folder here we have to call it layout. TSX just like the root layout down here but this layout will only be applied to our SL noes routes because we have it inside this folder and this layout will look very similar to the root layout so let's open this in a split view so we can compare them so in our nested layout we export a default function which we call a layout again the name of this component is up to you only the file name is important and in here we want to take the same argument as the root layer out the children which is whatever we render inside this layout then we add the body to this function and we return a fragment like this with empty angle brackets a fragment is just a way to put multiple components in here without having to declare another diff we could also create a diff but with a fragment we have one less unnecessary div instead whatever we put inside here will be put directly into this children tag basically and in here we put our nfar that we just created and Below we put the main tag which is the main content of the page in here I want to render the children which is the page under the/ notes route and the cool thing is we have pretty installed this is why I didn't pay any attention to the formatting it looks very ugly right now but when we press our formatting shortcut it all gets put in place it even adds the parentheses for proper formatting it puts the props into one line because they are short enough and this is why I really love Pria it takes care of all of this for you and you don't have to think about it but we also want to add some styling to this main tag again we do this with tailin class names with P4 we can add some padding of one Ram or 16 pixels to all sides then I want to give this a Max width of 7 XL with this class name so that the contents of the page don't go all the way to the left and right side instead it can only be a Max of 1280 pixels and we get these hover suggestions again because we installed the Tailwind vs code extension which is really useful so now that we have a Max width we also want to set the margin AO so that this main tag is sended on the screen okay that's our node layout and this will automatically be applied to all routes under slash noes and it will be nested inside the root layout so we have this root layout and there is our SL noes layout and then there is our notes page so when we go to our site again we can see our nafar up here and Below we have our padding and here you can see that this is Zend on the screen but on the front page we won't see the enough bar exactly as we wanted only on the SL notes route okay now let's finish setting up our nbar and for this I put the browser and the IDE in split screen so we can see our changes immediately so on this outer div we add some class names again P4 for some padding and then this Shadow class and again they are all provided by Tailwind this as the name implies add a shadow here at the bottom of the nafar again I want the contents of the knf bar to have a Max width so that they can only be as wide as the content below it so in this outer diff we add another diff and we can remove the nabar text and here we add a bunch of different class names first we make this a flex box and then we use this Flex rep class to apply this Flex rep CSS attribute this makes sure that on small screens whatever is laid out horizontally will automatically be put vertically if there's not enough room then we can use this Gap three value which makes sure that the elements in this Flex box will have a little gap between them so they don't touch each other right now we can't see this yet because we don't have any elements inside the stuff actually what we can do just for now h we can maybe put two spans in here which each have a text let's say element one element to just as long as we don't have anything else inside the stff let's format this and without this Gap there would be no space between these elements but with The Gap we have this little space which will be applied to all elements inside this Flex box to make sure that they are centered vertically we add item Center and then we also add justify between which pushes one side of the sff to the left and the other one and the other one to the right and then again I want to give this a Max width of 7 XL and M Auto to Center this and now our naar looks like this it has the max width and it has elements on the left and on the right side and on very small screens element tour will Auto automatically be pushed below element one for responsiveness nice since we added the prettier tnd plugin we can press our formatting shortcut and it automatically puts these class names into the recommended order which is just nice to keep a better overview again we set up all of this in the beginning if this doesn't work for you then you might have to watch the setup part of this tutorial again and make sure that you set up the project properly okay then I want to replace element one because instead of this text I want to show our logo and the name of our app like you can see it on most websites we want to wrap this into a link which we import from next SL link make sure to select the correct import this is the nextjs link we give this link an edge when we click it it will lead to SL noes we give this a class name as well Flex items Center and GAP one we know all of these attributes by now then we close this link and in here we put an image again this is an import from next SL image so you should have these two Imports now and then we want to import our logo here we have to do this manually import logo from the name of this variable here doesn't matter just a path to the logo and we have this in add/ assets slash and then here is the logo.png we want to use this logo as the source of the image we set an ALT text which was a flow brain logo this is for screen readers and then I want to set the width and the height of this image both to 4 here and nextjs will automatically resize this for us so that we don't load this logo in an unnecessarily large size then we close this image tag and then next to it so below the image tag but still inside the link tag I want to put some text which we wrap into a span yeah and this will just say flow brain and then we add some styling to the span just the font bold class which I think is explaining and there's our brand logo and when we click it we get to the/ notes page but we are already on this page okay and then we also want to replace element two on the right side but we want to put two elements on the right side so we put another diff in here which is placed directly below the link tag still inside this flexbox div so right here we put another div again we St the St again reflex box item Center and a gap of two and in here we put two elements the first one is the user button which is an import from cler when we close this we can see the clerk user button here which already shows our logo and this is this popup from cler we get right out of the box and then here we can do all the session management which just works we can also lck out we will do this later for now let's finish setting up our nfar in here we can do some configuration for example we can set an after sign out URL this is where we will be redirected to after we locked out we want to get redirected to the front page and then we can also style this user button a little bit more with this appearance prop again we add another pair of curly braces here again I don't care about the formatting because prettyer will take care of this for us in here we have this elements field again colon curly braces and then here I want to style the Avatar box I basically just want to change the size of this Avatar here so Avatar box column again curly braces here I want to set the width and height both to a string 2.5 RAM and the same for the height 2.5 Ram so when we save this it gets a bit bigger lastly I want to add the add note button to this layout we put it right below the user button still inside this diff here which is placed on the right side of the NF bar so this is an uppercase button uppercase because this is one of our shety n UI components that we installed earlier so this is an import from our components folder uppercase button the button will say add note there it is with the default styling and let's also add an icon here so inside the button tag but above the text we write opening angle bracket plus which is a loit react icon so loit is this icon package that sheds the UN installed automatically we can Define the size here Trend here and set the margin right with this Mr minus 2 class name close this and then we have a little plus icon in here so let's save this that's it for the nafar for now so now we can log out via our user button sign out we get redirected to the front page and now let's set up the layout of the front page as well we will keep this simple this page is located in our app folder this one here page. TSX we remove all the default code here here so everything inside the return statement and then we put a main tag in here and then we will just build a little page with a login button we will keep this simple so we add some class names to the main tag again we make this a flex box where everything is centered on the screen so flex but this time we also add Flex col to make this a flex column so that all elements are aligned below each other instead of horizontally we set the height to the full screen height we use item Center and justify Center to Center the contents on the screen and then lastly Gap five to add some spacing between the elements in here we put another div which will contain our logo and app name again so again this div gets class names as well Flex items Center and GAP four and in here we want to put our logo image again so again image from next image again we need our logo we can copy the import statement from our KN bar paste it here oops set the srz to logo set the same alt text as before flow brain logo this time I want to make the width and height a bit bigger 100 there it is and again I want to put a text beside it so we create another span which says flow brain and again we add some class names to the span first we set font extra bolt which makes this yeah EXT extra bold and then we set tracking tight which puts the letters a bit closer to each other which often looks good with larger text sizes because now we want to set the text to 4 XL which changes the text size but on large screens for example full screen view like this I want to make the text even bigger and you can handle responsiveness in tailent with modify FES so we can say on large screens with LG colum we want to use text 5 XL instead so from large screen onwards we will use 5 XL and LG as you can see here is 12224 pixels and everything below will use text 4 XL so let's see this should be text 4 XL because it's a small window and this is textt 5 XL this is this is just one way of implementing responsiveness into your Tailwind layers with these modifiers there are also other modifiers like SM colon for small screens ND for medium siiz screens and so on but these two break points here are enough okay cool then below this div still inside our main tag we put a paragraph tag with a description an intelligent no taging app with AI integration build with open AI pine cone nextjs shety n UI clerk you will see the full text in a moment so you can type it out yourself and more and now the cool thing about Pria is that it even breaks up these large texts into separate lines which I really like so you can pause the video and type out this text if you want of course you don't need it this is just so that it looks good and then we add some class names to the paragraph tag I want to use text Center which I think is self-explaining but I also want to make this a bit smaller here so that the text doesn't have such a large horizontal width what we use this time is Max width Pros Pros is a convenience class for text that yeah can be read basically so like blog post or any texts we could also use something like the 7xl that we used earlier but Pros is just a nice class for text that should be read by users so this is the max width it has now okay and the last thing I want to add to this screen is a button that brings us to the notes page or to the login screen rather so here here we use a shed CN button again which will just say open but instead of a button I actually want to render a link because this will just link us to sln notes and you shouldn't use buttons as links for accessibility reasons if you link to a page then you should use a link HTML tag but we still want the button styling right and chn has a functionality for this implemented we can add this as child prop to the button which tells this button to not render an actual button HTML tag but instead render whatever we put inside these tags here and inside them we put the next link with an edge W to/ notes we wrap the closing tag of this link around our open text here we can also make this button a bit bigger with the size is prop and here we can select LG as a string and now this is still styled as a button but it's actually a link so if we take a look at this HTML component you can see that this is an AAG so a normal HTML link but styled as a button so now it looks good but it's also good for accessibility for example if someone uses a screen reader one more thing I want to do on this page if we open this page but we are already logged in I want to get automatically redirected to the/ notes page you don't have to do this I just want to show you how to do this remember that all components and pages are server components in nextjs 13 and 14 by default so whatever code we put in here will be executed on the server and only the finished HTML will reach the client meaning that if we redirect in here this will happen before the page is even opened this way there will be no flash of the homepage we will not see the homepage for a split second before we get redirected we get redirected before we even see the page which is exactly what we want so what we do is up here we want to check if the user is logged in for this we can call this off function from clock nextjs this is not an async function so we don't have to await this but in here is the user ID which we d structure between C braces if this user ID is defined we know that we are logged in so we can check if we have a user idea then we want to redirect which is an import from next SL navigation to/ noes so if we are logged in we want to redirect to the noes page so let's save this we are not logged in currently when we click open we get to the loog in screen going to log in with my account we will see the notes screen when we try to open the front page now we will get redirected immediately to the nose page because we are logged in if you don't want this functionality here you can remove this code here but I think it's pretty cool and similarly if we log out on the notes page we get redirected to the front page and we have to log in again yeah so now our whole authentication flow works as I told you earlier we can also upload a profile picture into our clock account I'm going to upload this beautiful flattering photo of mine just to show you that this works as well and it's updated in our app immediately nice and now we can start creating notes and each note will be tied to our user account so that every user has their own nodes okay before we add our AI chat I want to implement the basic CR functionality so that we can create nodes update them and delete them for the backend logic we create an API route Handler for this we right click on the app folder create a new folder called API in here we put a folder called noes to put this endpoint under the/ notes URL and and in here we put a route. TS to create an API route Handler if you have no idea what a route Handler is again I explained this in my nextjs beginner tutorial we could also use server actions to handle the backend logic server actions are stable now but server actions don't support the response streaming that we want to implement for AI responses later and I didn't want to mix server actions and regular API endpoints in the same project so I decided to go with API routes all the way and to create a post end point here we have to export an async function note that this is not the default export it's a regular export that's important andent Rec call it post in all uppercase and this will take a request as its argument okay this will be the end point to create a new note first of all we put a try catch Block in here because we do database operations and they can always go wrong if they go wrong we want to lock the error to the console and we want to return an error response which we do it with this response class with an uppercase R and we have this Json convenience function on here where we can return a a Json response to the front end this Json will have this error key with an error message which will say internal server error this is just meant for users to be readable and after a comma we add the configuration where we can set the statos code for this response which will be a 500 for internal Zera error okay back into the tri block here we want to get the request body so the data that we sent to this endpoint we create a con body and we can get it from await rec. Json this Json body will contain the note later so the title and the content of the note that we want to store in our database remember that in our schema the title of the note is mandatory we have to pass one and the content is optional but on our back end we have to verify that the user actually sent a title string in this body because they might make a request where this title is missing but then we can't make an entry in our database this means we have to validate the body we could do this manually by accessing these fields and checking if they are a string or not but a better way is to use a proper validation Library which is especially useful later when you have larger schemas that you want to verify which can also be a bit more complex so let's do this properly and earlier we already installed this sort Library this was actually installed by shedy Nui because it also uses this Library under the hood for form validation sort is the most popular validation library and we can also use it to valid dat our request body here so we have to create a schema for our note and I actually want to reuse the same schema here in the back end but also on the front and later for our form where we type in the title and the content because they both have to follow the same schema so let's put the schema into a separate folder we can put it into the lip folder in here let's put a new folder that we call validation and in here we put a file called node. Ts for node related validation so from here we export a const which we call create node schema and we assign this to Z and Z is coming from zot we have to import this import Z from the zot package and on here we can call this object function with which we can create a schema for a node this object has to have the same form as our node later so in here we put a title because this is what we expect in our node schema and then we can create validation rules for this title we can say this should be a z do string which is a function this will validate that this value is of type string but the string should also not be emptier to check this we can P this do Min function and pass the number one to it so this title string has to have at least one character still inside this Min function we add a comma and between curly braces we can do some configuration for example we can customize the error message because the default one is not very user friendly we want to set the error message to a title is required and this is also what will later be shown in our form as the error message if we try to submit a note without a title but you will see this later after the title we add the comma and also add validation for the content of the node again this is a z. string but this time we append this optional function because remember the content is not required we don't have to pass a body for the node and now we can use this schema to validate our input whenever we want to create a node and there's also another cool thing we can do we can create a typescript type from the schema which we also want to export here export type let's call it create node schema again but with an uppercase here equals c dot then we call this infer function and between angle brackets we write type of create node schema like this and now when we hover over create notes schema we can see the typescript type and we can use this for type safety on our formulator and now we can use this schema to validate the body in our post route here for this we create a const which we call pass result equals then we import our create node schema which we just created call do save Pass and Pass the body to it this will now check that the request body adheres to the create node schema we use Save pass because we want to throw our own error with our own error message and not use the default one so below we check if exclamation mark pass result do success so if the validation failed then first of all we want to lck an error to the console we want to lock the pass result error and then we want to return an error response to the front end so again we call response. Json again we put an error key in here which will say invalid input and again we set the status code but this time to 400 which is the HTTP status code for invalid request if we get below this if check it means the request body was valid there is a title in there and if there is content in there it was a Str so now let's extract these values we can destructure them from the pass result. data and in here we now find the title and the content with all the completion but when we create a new note we also need the user ID right we already know how to get it we can get it from this clerk off function which is not async so we don't have to await it but in here is the user ID again the user ID can be undefined if no user is currently logged in so we also want to check that the user is authenticated so we check if exclamation mark user ID then we want to return another error response again with the error key which will say unauthorized if we are not logged in we are not allowed to create a Noe and the status code for not authorized is 401 and then below this if block we now know that we have a valid title content and user ID so we have everything we need to create a new node so let's create a const node call await Prisma which is an import from our lip folder the Prisma client Singleton we created earlier on this we can access the note model if you don't have the node model on here then you didn't generate the Prisma client in this case run npx Prisma generate again you always have to do this after making changes to your Prisma schema otherwise this client here is not created properly okay on note we can call create parentheses cly Braes then we have this data field where we put the values of the node which are the title the content and the user ID everything that a note document in our database needs and the last step is to return a success response to the front end so again return response. Json in the Json body between curly braces we put the note it's good practice to return the document that you created back to the front end so that we can work with it there if we need to and res set the status code to 2011 which basically means new resource created successfully and that's our create node endpoint before we create any nodes we also need a way to see the nodes on the screen right so let's go to the notes page where we want to show them and for now we will keep this simple we will create an elaborate layout later but for now I just want to get something on the screen as quickly as possible so again pages in the next JS app router are server components by default which means that we can make them async and just fetch data in here and this data fetching will be executed on the server and only the finished layout will be sent to the client this means we can fetch the nodes directly in here we only want to get the nodes for the currently logged in user right so again we call this clerk off function and get the user ID out of here now since we have this middleware that protects this/ noes route the user ID should always be defined here because we can only reach this route if we are logged in but typescript doesn't know this typescript still thinks that the user ID could be null so what we can do is we can check if exclamation mark user ID and since this is an unexpected State we can just throw an error because this should actually never happen but if it happens for some reason this means we made changes to our middleware accidentally so let's throw an error that will say user ID undefined and now below typescript knows that the user ID is defined so we can create a const all nodes call await Prisma which again we import from the lip folder do node do find menu and in here we add a where filter where the user idea has the value of the authenticated user so that we only fetch nodes of the currently logged in user and again I want to keep the layout simple for now so we just remove this here will be your notes text instead I want to put an expression in here between curly bracers and for now let's just Json string ify all noes so we will just see them in plain text form as soon as we save this we see an empty array here because we don't have any nodes in our database yet to a create a new note I want to add a model dialogue that appears when we click the add note button so let's create a new component for this and I want to put this into the components folder because we might want to reuse this dialogue in different places later let's call it at node dialog. TSX and we export a default function with the same name for the dialogue of course we use our shcn component that we already installed this is how it looks the usage is described in the documentation and for the input form we use the form components of shed CN which we can find here this is basically a repper around the normal HTML input form it can look a bit complicated at first but the benefit of this is that we get proper input validation where we can show different error messages in the UI if the input is not valid it also adds proper accessibility features for screen readers and it makes accessing the data of the input Fields easier under the hood this uses another Library called react hook form it also installed this to our package.json earlier when we set up the shet CN components this is the most popular form validation library for react and it also uses s for validation so let's go back to our project and set this up first of all this component needs props so we go above we create an interface which we call add node dialogue props you can also give this any other name this is just my personal preference to it we pass open as a Boolean this way the parent that uses this dialogue can decide if the dialogue should be shown or not and then also this set open function which will also take an open Boolean but doesn't return anything we will trigger this callback when we click the close button inside this dialogue and then the parent can change the open state to hide the dialogue okay we want to pass both these props to the add node dialogue so in here we destructure the add node dialogue props and in here are open and set open then we write const form and Rec call use form this is an import from react hook form that I showed you a moment ago this is the form validation library that ched the N uses together with its form components to use form we can pass a type argument between angle brackets and in here we want to pass our create node schema with an uppercase C this is the schema we created in our validation file and this tells react hook form what data we expect in this form then we call this use form function with parentheses and in between curly braces we can do more configuration we want to pass a resolver here the resolver is what does the actual form validation here we can use the zot resolver which we have to import manually import curly brazes and we should find it from at hook form resolvers which we installed earlier SL sort and in here should be the zot resolver we call this zot resolver here and pass our create node schema but this time with a lower case here because this is the actual sort schema not just the type so now react hook form we use sort to validate our creat not schema for our form input below still inside the component we create an async function which we call onsubmit and here we will receive the input of our form again of type create node schema because this is the data we expect title and optional content for now let's just keep this empty or maybe let's show an alert dialogue that just display the input to see that this works and we will fill this on submit function with code later but below of course we want to return the actual component the dialogue that contains the form so again the syntax of these dialogues is described in the shed CN documentation but you don't need to read this you can just follow along opening angle bracket dialogue and here is something important you always have these two options the radic import and the one from our UI folder you always want to use the import from the UI folder because those are the shedy N components the radic one are the unstyled ones that shed CN uses under the hood so make sure to always select the correct import so we import the shed CN dialogue which takes two props open which is one of the props we have passed to this dialog component and onopen change to which we pass set open the first one decides if the dialogue is shown and the other one will be triggered for example when we close the dialogue via its close button so that we can hide it from the UI we close this and in here we put dialogue content again it's important that you import the component from the UI folder this doesn't take any props and we close it and in here we put a dialog header and in the dialogue header we put a dialogue title and this is all responsible for proper styling and the title will say add note let's save this for now and let's already show the dialogue in the UI so that we can see it while we are building it I want to render the dialogue in the n Bar where our add note button is so we go into the nbar component and we put a state at the top of the component so scrap brackets show at node dialogue and the corresponding seta set show at node dialogue and we assign this to a use state which we initialize with false as soon as we save this we should get an error because we can only use state in client components and by default components in the nextjs app router are server components to return this component into a client component we go to the top of the file and add the use client directive if again I explained this in my nextjs app rouer beginner tutorial and to render the dialogue in the UI we surround this whole diff inside a fragment like this and below the diff inside the fragment I want to render the at node dialogue which expects the open and set open props so we said open toour show at node dialogue that's the state that decides if the dialogue is shown and for set open we pass the state Setter so that we can hide the dialogue now if you have watched any of my previous tutorials then you might also know that we could unmount the dialogue like this we could use our opened State and then decide if we render the dialogue or not the difference between this approach here and the approach where we use the open prop is that this approach approach will completely unmount the component and this will also clear any input that we typed in this approach here basically just hides the dialogue but it's still mounted the benefit of this is that the input is still there after closing the dialogue and this is the effect I want to have here I want to keep the input even if we close the dialogue so that we can continue writing our node at the later point but this is just personal preference okay and then we simply want to tockle the state when we click the add node button right so we add an onclick Handler to this button we add an error function which we call Z show at node dialogue and Z is to true and now when we click our add note button we see our dialogue we can close it either over this button or just by clicking outside but this Behavior can be customized so now let's finish setting up this dialogue below the dialog header but still inside the dialogue component we want to put this form component which is an import from our UI folder not the react hook form one this is chedy 's form component which just makes setting up these forms a little bit easier the syntax of this is described in the shed CN documentation for example on the input component when we scroll down we see the section that says form which describes how to wire this up and we have a similar entry in the text area component and all the other form components but again you don't have to read this because I will show you how to set this up so to this form opening tag we pass curly braces and we spread this form variable from up here because this react hook form object contains different values control get values register and so on and this shedy and form component uses these props we don't have to understand what exactly it does with them internally but it just works okay into this uppercase F form we put a lowercase f form which is just a normal HTML form but this form will be managed by this shy n component to this lowercase form we pass the on submit function to which we pass form dot again form is coming from react hook form here we call handle submit parenthesis and to this we pass our onsubmit function which we created earlier this is just the syntax react hook form users to wire this all up the benefit is that later we get our create node schema values in this on submit function the title and the content usually in HTML forms you get a form event and you have to extract your data out of there we don't have to do this instead we get the data we expect directly this is just one of the benefits that such a form Library brings and I also want to add a class name to this form to add some spacing between the single form elements we add this space Y3 utility class this works without using a flex box and it adds some vertical spacing inside this form we use another shyn component which is this uppercase form field which we import from our UI folder this gets a self closing tag and this component controls a single input field in our form again the syntax of this is described in the Chet CN documentation so this expects this control prop to which we pass form. control then this expects the name of the form and autocomplete should help us because we already told react hook form that we expect a create node schema so we get Auto completion here the first one is for the title and then we have this render function which renders the input field itself so to this we pass parentheses inside the parentheses another pair of curly braces because we want to destructure this and in here we have this field value and this returns an error function make sure the syntax is correct the right arror another pair of parentheses because we want to return a component here and don't worry the syntax doesn't get any more complicated we only have to render our input field in here and that's basically it so in here we want to put a form item which again is coming from our UI folder we close this tag a form item contains a form label which is the label that is shown Above This input field this one will say note title and then below we put a form control and all these components are necessary for proper styling and input handling and inside the form control we put an input with an uppercase i the input again gets a save closing tag when we save this we already see the in the UI we can give this a placeholder just like a normal lowercase i HTML input field this is just a styled one the placeholder will say note title there it is and then we D structure this field value inside this input component this will wire it all up so that it actually works one more thing we need to add below the form control we put a form message this form message will show the error message if we try to submit this with invalid input so without the title and the cool thing is this just works this will automatically show the error message of our create no schema which is this message here and this just works without us having to handle this manually this is the cool thing about these shedy and form components the syntax can be a bit complicated the first time but it gives us a lot of cool functionality out of the box and again you basically just copy paste it from the shety and documentation I don't understand all of these components under the hood I just know that they work if you use them properly anyway this is the full Syntax for a single form input field and now we simply want to duplicate this form input field to create another one for the note content right so let's just copy paste this one time below the control prop is still the same we Chang the name to content we Chang the label to note content and the placeholder as well when we save this we get a second input field but here we want to use a text area instead of a normal input field uppercase tier because this is a larger input field and we can resize this but the rest works the same and again this is connected to our react hook form and our create node schema now we only need a submit button which we put directly below our second form input field still inside the form for styling we can wrap this into a dialogue footer which puts this at the bottom of the dialogue and in here we want to put a button that submits this form but I want to create a button that has a loading State while the form is submitting so that the user doesn't press it twice the normal shedy end button doesn't have a loading state so let's create our own loading button I put this into the UI folder because this is where shed the inputs all these Primitives we could also put it one level up into the components folder doesn't really matter and I call this loading minus button usually I use camel case for components but since shed CN uses lower case with spaces I use the same naming convention here just for consistency but it doesn't matter you can use any file name you want and in here I want to export a default function called loading button this button needs props so again we go above this time we use type instead of interface you will see why in a moment and we call it loading button props equal curly brazes in here we pass this loading Boolean which will decide if the loading state is shown and the reason we use type instead of interface is because then we can use a union type this way we can basically extend these components what do we want to extend them with we want to extend them with the button props which are coming from our button sheds the end component because this way the loading button will take the same props as a normal button plus our loading prop and then we want to destructure these props here so we add curly braces and res set the type of the props to loading button props in here we will find the children those are part of the normal button props this is whatever we put between the opening and the closing tag of the button we have our new loading prop and we have the remaining props which we can declare with dot dot dot props those are all the other button props except for children and loading and then we want to return a special button here right so return opening angle bracket button just like any other button this will render the children but next to it it will also render the loading indicator so we add curly bracers we check if loading is true to emberson signs if loading is true we want to render the loader to Icon which again is a LED react icon and then we style this with some class names MR2 for some margin on the right side H -4 and W -4 for the width and height we could also set them with the size prop as we did earlier but this basically has the same effect it's up to personal preference and right now I don't know what the size value for one ram is so I keep it like this and then we can use this animate spin class which is another Tailwind class that rotates this loader so that we get an actual animation and then we close this so now we have our special loading button and here on the opening tag of the button we also want to spread all the remaining props so any props that we pass to this button should be applied and then we add one more prop we set a disabled state of the button this button should be disabled either if props do disabled as true so if we just set the button to disable directly or to Vertical bar if loading is true if the button is loading it should also be disabled let's save this and then we can use this loading button just like a normal button we want to use it in our form here so opening angle bracket loading button the one we just created it will say submit there it is the dialog footer wrapper is responsible for laying out this button this is why it has the full width then we set the type of the loading button to submit this is a normal button prop and it TS react that this button should submit the form when it was clicked which will then execute our unsubmit function and the button also expects the loading prop when do we want to set this to true we want to set this to True while form do form state do is submitting is true this is coming from RE hook form and this value will be true as long as our unsubmit function here is running so it's an Asing function and until this function returns this loading state will be true and we will show the loading state for our button okay great so our form is all set up the last step is to actually submit our data for now let's see if the alert works so first let's try submitting this form without a title we see our error message here but we don't see the correct error message this should be title is required and not just required right and I know why this happens because this input is not a string with a length of Zero by default it's completely undefined by default when we type something in and remove it again we get the correct error message to fix this we have to add a default value to use form and I also saw this in the shed CM documentation so we add this default values key to the use form function and in here we set the default values for both the title and the content to an empty string so that these values are not undefined now let's refresh this and try this again yeah now we get the right error message right away okay and when we type in a title we should see the value in the alert dialogue I didn't stringify it let's stringify this right cck Json stringify input we will replace this in a moment anyway and now there is the title and the content and now we want to use this to create a new note right so let's remove the alert call and finish the onsubmit function we do a fetch request to our back end here which can always go wrong so we wrap this into a triy catch again we want to lock any errors that happen here and if something goes wrong I just want to show an alert dialogue it's not the greatest choice for UI but I want to keep it simple in this tutorial you can also add a proper toast or an error message to the UI but I want to set this up as quickly as possible to focus on the important stuff so this alert will say something R wrong please try again a nice generic error message in the tri block REM make a request to our backend route Handler that we set up earlier so we create a const response a call AWA fetch just the native JavaScript fetch function to our endpoint which we put under / API we don't have to add the base URL in nextjs it preens it automatically and the route was / API SL noes and then comma and then we add the configuration of this F request we set the method to a post to make a post request and our endpoint expects the node content in the body right so we add the body we have to stringify it to send it over the wire and in here we simply want to send the input which contains the title and the content okay almost done below cons response we check if exclamation mark response. okay which means that we get a 400 or 500 response back then we want to throw an error here because something went wrong right and in this error we put a string that says status code column and then we append response. status and we can see what exactly went wrong in our console later if this request was successful F below I want to call form. reset which resets the input Fields so they are empty again for the next node and then we also want to update the UI to show the new node immediately right and this page remember is a server component we fetch all the nodes server side so we don't have any state in here in order to refresh a server component we simply call router. refresh which basically fetches the data again some people think this is not optimal because we fetch all the notes of this page again but it's totally fine and this is how you do it in zver components right now you just fetch the data again because if you want to do more granular State updates like putting a single node into our UI then you need state but you can't have state in server components so instead we basically refresh the screen for this we need the router the router can only be used inside client components but since the nfar is already a client component and the nbar uses the ADD no dialogue we don't have to add the use client directive here we can if we want but it's not necessary and at the top of our at node dialogue component function we create a const router and call use router careful there are two different Imports we have to use the one from next / navigation because the SL router one only works in the old Pages directory not in the app router so now we can use the router in this component and Below form. reset we want to call router. refresh as I explained earlier this will refresh the server component and then lastly I want to close the dialogue so we call set open and set it to false this is the Callback we pass to the dialog which then changes the state okay this was quite a bit of code and Concepts but these things are important now you know how to use chaty and form components now let's try adding a note so again we click this button again we have our input validation let's say my first note this is my first note submit which should make a request to our / API noes endpoint we have our loading State and after we submitted this we automatically refresh the server component and and this is the data of our new node that we created which is also still there after refreshing the page amazing just to make sure that our user ID filtering works properly let's log out with this user and log in with another one I log in with my other Google account which I think I haven't used in this app yet here it creates a new account and the array is emptier as it should be and as you can see if the account doesn't have a profile image it automatically uses the initials of the name which is another quick clock feature okay sign out again into the other account and we should see our note again okay now let's create a component for these nodes that actually looks good okay let's create a note layout in our components folder so right click on components and if you're wondering why I always have to click two or three three times my mouse is broken anyway in here we put a node. TSX and as usual export default function with the same name of course we have to pass the node data to this node so we create an interface for the props node props and to it we want to pass a note entry import this note type from Prisma client this is automatically gener ated from our schema and it contains all the data of a node from our database including the time stamps and the IDS now the component and this node type have the same name and to avoid confusion I want to rename this import which we can do with S I want to rename this to node model and this is the name we use here for the prop okay cool then we destructure these props here and then let's set up the layout first of all I want to create a variable called was updated this will be true if we updated a note so that we can show a little updated indicator just so that it looks better for this we can simply check if the node updated timestamp is greater than the node created at timestamp when we create a new node these two will be identical if we update a Noe the updated timestamp will be set to the moment when we updated it simple and in the note I want to show either the created ad or the updated ad Tim stamp in user readable form so we create a const created updated at timestamp this will contain one of these two values here we add parentheses inside we check if was updated is true then we want to use note. updated at otherwise colon we want to use note. created at and I wrap this into parentheses because on either of these two values I want to call to date string which turns this into a human readable form which we can then show in our UI and then below we return the UI for this I want to use a shyn card which we import from our UI folder again the syntax of this is described in the shyn documentation into the card we put a card header into the card header we put a card title the card title will render the note title of course and before we finish this let's save it and put this in the UI so we go to our notes page where right now we just stringify the nodes instead we add an expression take our all nodes array and then we want to map this to a layout so map in here we take each note and render a UI element the element we want to render is of course our node component that we just created which expects the node as input and since we call the map function here we also have to add a unique key for this we can simply use the ID of the node which is unique between each node okay and when we save this we already see our node in the UI it has the full width right now but instead I want to render a responsive grid so we go to this outer div here add a class name prop we use the git class we add a gap of three to add some gap between the different notes maybe let's add a second note so we can see this difference my second note blah blah blah submit the UI refreshes but we still have our list to show multiple columns we have to use the grid cols class for example git cols to creates two columns but I only want to use two columns on small screens and upwards on very small screens I want to show only a single column so we add the SM modifier here so now this depends on the screen size this is extra small and this is just small and similarly on large screens through LG colon I want to use git called three and just like that we get a responsive grid two columns and eventually we get three the last one will be placed here on the right side third node oops blah blah blah there's the third one and this is how the grid is responsive when we press our format shortcut it all looks nice if our notes are empty I also want to render a text that says yeah you haven't created any notes yet or something like that so we another expression below our map call we check if all nodes. length is equal to zero two emberson signs parentheses then we want to render another diff which will say you don't have any notes yet why don't you create one but we have to escape these single quotes we can either use this special value here but it's not very readable what we can do instead is we can turn this into a string expression this should also work just to see that this works let's change this to not equal to one for a moment and let's comment this out I will add this back in a moment there's our text but it's inside the grid so it only takes up 1/3 which looks not very good but we can change this by adding some styling to this div we add call span full which makes this take up the full available width of the grid and with text Center we can Center the text so now it's in the middle much better then we set this back to equal zero and we remove the comment up here so that it's displayed properly okay great now let's finish the note layout so still inside the note header but below the title we add the card description which will contain the created updated at timestamp which is already formatted to a readable text and then if was updated is true so if the updated at Tim stamp is shown I also want to show a text that says updated in parentheses behind it so still in inside the card description we check if was updated is true then we want to render a string with a space parentheses and this says updated we can't see it yet because we can't update notes yet but we will see this later okay great and lastly below the card header I want to render the card content in here I want to add a paragraph to which we add the class name whitespace preline this way we make sure that any line breaks and any formatting is maintained in here so if we write our note over multiple lines like this then it will also be shown like this here in the cards otherwise it will put everything into one line which doesn't look good yeah and in this paragraph tag we simply render node. content and that's it now we have our properly formatted node inside a responsive grid and the next step is to add update and delete functionalities for our notes after all of this base functionality is done we can finally Implement our AI chatboard to update and delete nodes we need new route handlers so let's go into our no routes. TS file where we already have our post endpoint and now we add two or more endpoints so again export asnc function we use a put request for updates we could also use patch but since we replace the whole note I think put is more idiomatic but it doesn't matter this receives the request just like the post function and again we have a tri catch block and we use the same error response as in the post end point the rest looks very similar to what we did in the post endpoint we want to get the body and then we want to validate it but we actually need a new validation schema because when we update a note we expect the same title and optional content but also a node idea right so that we know which node we have to update so let's add a second schema export const update node schema and the cool thing we can can do in zort is we can reuse the schema that we already have and then extend it so we take the create node schema call extend parentheses curly bracers and then we can add another value to it we expect an idea which is a z. string which again should not be empty so we add Min one now we could also validate that this is an actual object idea and it has the structure of a DB object idea but this is unnecessarily complicated we can also just check that we pass a string for the idea and if the IDE is not valid it will not find an entry in the database we don't need to export a type for this because we don't use this inside a form but we also need a validation schema for deleting nodes so let's create another one export const delete node schema this time we don't extend the existing schema because this time we only need the idea so we create a new c. object and in here we put the same idea value as up here okay then let's go back to our route we can actually copy more code from up here so we don't have to type so much let's copy everything from con pass result to right here above where we created the node so let's copy this and and paste it down here we just have to make a few changes we don't want to use the create node schema here we want to use the update node schema we can still use the same error message invalid input from our D structuring declaration down here we can now also get the node idea now before we get the user IDE we can also check that this node actually exists otherwise we can't update it right so let's create a const node and assign this to a Prisma do node. find unique parentheses curly braces for the filter we use where ID so where the IDS match and then below we check if exclamation mark note so if this no does not exist then we want to return another error response another Json which says error node not found and the status code for not found is 404 so let's use that here then below we check that the user is actually authenticated but additionally we also need to check that the user ID is actually authorized to addit this node otherwise any user could try to edit any node so we check if EX information Mark user ID or if user ID is not equal to node. user ID so the user ID who created this node then the user is not authorized to update this node okay then we need to actually update a Noe and return a success response so const updated Noe equals ARR Prisma do node. update parentheses C races we need the wear filter where the IDS match because we want to update a specific node and for the data we pass the new title and the content which is part of the body that we sent to this endpoint okay and then we want to send a success response back return response. Json in this Json we put that updated Noe and for the status we use 200 which means success but not 2001 because we didn't create a new node we just updated an existing one and then we also need our delete endpoint and for this let's actually copy paste the whole Port endpoint so that we don't have to write it from scratch we change the name of the function to delete to create an HTTP delete endpoint and post put and delete are all HTTP methods those are all methods that we can use with the fetch function these names are not arbitrary they are only very specific names you can use here okay we want to pass the input from the delete node schema and the delete node schema does only contain the ID this is why we see an error down here we still do the same checks if the node exists and if the user is authorized to make changes to it but then we only want to delete it and we will also not return it to the front end so we just call await Prisma do node. delete with the wear filter for this idea right and instead of the updated node we just return a message that says not deleted with the same 200 status code yeah they should say not deleted not not deleted to update a node I want to use the same dialogue as we Ed for creating a new node so I want to make some changes to the add node dialogue and I actually also want to rename it so we click on the file press f2 on Windows otherwise right click and click on rename here and I want to rename this to add edit no dialogue it should automatically update the import statement in enough bar yeah but we might have to save the changes otherwise you have to fix the import statement yourself up here then I also want to rename the component to add added node dialogue and a props add added node dialogue props of course the name doesn't change anything in its functionality but I want to name it properly and let's also rename this state in our nafar again with F2 show at edit note dialog just for consistency and Z show at edit node dialogue for now we close the enough bar and what I want to do is I want to pass an optional note to add it to this dialogue if we create a new Noe this value will simply be undefined if we update a Noe it will contain a note so we add another prop not to add it we make it optional with a question mark and this will be of type nodee which again is coming from the Prisma client this is our node type we destructure this new note to edit prop down here and then we can use it to fill the default values of our form so right now we only have empty strings here right if we have a note to edit then we want to use the title as the default value if not we want to use an empty string as before and the same for the content not to edit content to Vertical bars empty string so if we open this dialogue with a notes to edit then the title and the content will automatically be filled with the contents of the note we want to edit let's save this for now and before we continue let's try this out let's go to our uppercase node component because I want to render the update dialogue for each node in the node component itself the cool thing about this is that each node has its own state so we can maintain the input for each node even if we close the dialogue because each node has its own dialogue for this we put a state into this node component const scrap records show added dialogue and set show addit dialogue and we initialize this with a US state of false again to render this dialogue we wrap this into a fragment and below the card we render our add edit node dialogue the same one we also render in the Naf bar for open we pass our show added dialogue state for Z open we pass the Zeta of the state and then as opposed to our nabar add node dialogue we also pass a note to addit here which is the node that we render inside this component so now that we have state in here we have to make this a client component as well because again we can only have state in client components so let's add a directive and then we also need a way to open this dialog right I want to open it when we click on a card and I also want to add a shadow when we hover over a card and set a proper cursor so let's start with some Tailwind classes with cursor pointer we can turn the cursor into this one here that we also see when we hover over a button this little hand then we use the hover modifier to set a shadow LG but only when we hover over a node but this doesn't have any animation right now we can change this by adding the transition Shadow class like this now we have this hover Shadow with an animation and now when we actually click we want to open the added dialogue so let's add an onclick Handler which will call set show added dialogue and set is to True right and now when we click on any of them we open still the add node dialogue we will fix this in a moment but as you can see the input is automatically filled with the existing node and since each node maintains its own State we can actually type something in here we can click on another note we can type something in here as well and each of them maintains their own input which is really cool because they don't share one state each of them has their own dialogue rendered inside the component okay let's go back to our dialogue and implement the up date request so when we submit our form we want to add an if check at the very top that checks if there is a note to addit in this case we make a put request otherwise we make the same post request as we had before so we cut out everything from the response all the way to form. reset we keep router refresh and set open faults where it is because we want to execute this in both cases no matter if we create or updated a node and if there is a node to addit in the if block we want to make a put request instead so we create a const response await fetch to the same endp point SL API SL noes but the configuration is a bit different for the method we use put this time and for the body we pass json. stringify again but this time we pass a JavaScript object here between curly bracers because this time we not only need the title and the content but also the ID of the node right so we pass the ID here in this object which is the node to edit idea this is what our backend route Handler expects for a note to be inside the request body and after a comma we spread the input here again we need to check if the response was okay so we copy paste this line and this time we don't need to reset the form right so this is actually all we need and this should already work right so let's click a note to open the update dialogue my first Note One 2 3 submit and we see our updated title and this little updated text here really cool now let's also add an option to delete a note this is not handled in onsubmit it gets a separate function async functional delete node still inside the add edit dialogue this expects a not to edit right so we check if not to edit is not defined then we just return from this function but we will also not show the delete button if no to edit is not defined again we make a fetch request so we do this inside a tri catch block we copy paste the same error handling as up here and then we also need a loading State for the delete operation the loading state for post and update is handled by the form but for deleting a node we need our own State let's put this at the top of the component right here const scrap brackets delete delete in progress and set delete in progress and as usual this is a use state of fults okay back down to our delete node function at the beginning of this function we set delete and progress to true and then in the finally plock of the tri catch because we want to execute this in either case we we set delete and progress back to fults and then we make a fet request to our endpoint so as usual we create a const response a wait fetch to the same URL SL API SL noes the method this time is delete and again this is a valid HTTP method and in the body we we have to send the ID of the node we want to delete again we have to Json stringify this in here we exp the ID field again this is what we set up in our validation schema on the back end earlier and we want to pass the ID of the node to addit and then the same as before we check that the response was okay otherwise we throw an error and then we also want to refresh the screen and and close the dialogue and that's it now we only need to put a delete button into our UI so let's go down to our existing submit button here and above we add an expression which checks if not to edit is defined then we want to render another loading button here which will say delete note there it is if it looks better on larger screens when they are next to each other I don't know why the dialogue fotter doesn't add a spacing between them by default we can add one ourselves with a gap for example of one maybe this is a bit better than before but this also adds an additional gap on large screens so let's say Gap one on small screens but from SM onwards we want to set the Gap back to zero let's see yeah now we still have our gap on very small screens on XS screens but small and upwards we remove this Gap this is better and since our loading button uses the same props as a shed the end button we can also pass the same props to it and one of them is the variant which can change the styling of this button for example we have ghost which looks like this we have out line but for this delete button we want to use destructive which creates this red button then we have to add the loading prop this button should be loading as long as delete and progress is true but we also want to disable this button if we are currently submitting a note so we added disabled prop here as well and the button should be disabled if form. form state. is submitting the difference is that disabled will not show the loading indicator because when we are submitting the form we are not deleting a note right and vice versa down here in the submit button we set it also to disabl it when delete is in progress and of course we need an onclick Handler on this button because this doesn't submit the form Instead This should execute our delete node function and we can also set the type of this to button this will prevent that this button submits the form like our submit button down here does one more thing I also want to change the title of the dialogue depending if we are creating a new node or deleting an existing one so instead of hardcoding at node we turn this into an expression where we check if we have a Noe to edit in this case we want to render edit node and otherwise add node okay cool now let's try this out but I don't want to delete my first note because it has special emotional value to me maybe let's create another note and then delete it so we click it we can update it the same as before submit it's updated and we can delete it which should have its own loading indicator and boom it's gone now normally you should add some kind of confirmation before you delete a note you should not delete it right array or add an undo button alternatively but here I didn't bother because I wanted to keep this part of the tutorial simple and not add a thousand other dialogues and functionalities so you can do this yourself you can add another confirmation dialogue before the user can actually delete a note but yeah now the CR part of our app is finished there's one more step I want to do before we add our Ai chatboard and that is implementing the Dark theme because it looks really cool how to set up a duck theme with shety nuui is also described in the shety N documentation we already installed this next themes dependency in the beginning next we have to wrap this next themes provider into a client component this is necessary because this used client directive is not contained inside this next themes provider which is coming from a third party package so we have to wrap this into a client component ourselves so let's go back into our project we will use the theme provider on our root layout so let's put it into the same folder directly into the app folder here we put a theme provider. TSX file at the top we add the use client directive but we can actually use a simpler syntax than is described in the shyn documentation we can simply reexport the theme Provider from next themes so we destructure this here and we export it we don't import it this way we can use this theme provider inside our server components because we added this use client director here so this is all we have to do in here and then we rep our root layout with this theme provider we can put it here inside the body here we render the theme provider and we put the closing tag below the children to the opening tag of the theme provider we add this attribute prop and reset this to class but I'm getting an error because this is the wrong import statement here we don't want to import the theme Provider from next themes we want to import it from our re-exported file and now it works and this class prop here means that when we change the theme next themes will automatically set a class name on the out the HTML tag of our whole website when we enable Dark theme it will set this class to dark when we look into the Tailwind config JS file we have this dark mode class value up here this tells Tailwind that it will look for the dark class on the HTML tag which next themes handles for us and this will decide which theme we show light or dark the theme colors themselves are defined in the global CSS file so we have our primary and secondary colors and so on and down here are the dark colors which will be applied by TN CSS we can also add a third option fing back to the system theme of our device but I didn't implement this here because I wanted to have a Syle button with two states instead of a dropdown menu so we will just have a dark and a light theme Here Without the file back to the system theme if you want to add this as well you can take a look into the shyn docs okay now let's create a button with which we can actually toggle the theme and I want to put this into the components folder again because we might want to use it in different places theme toggle button. TSX export default function theme tockle button at the top we destructure a const for which we call the use theme hook which is coming from the next themes package we C this and this gives us access to the current theme and the set theme function and then from this component we return a shy nuui button which will let us toggle the theme we add some props to the opening tag of the button we set the riant to outline you will see how this looks in a moment we set the size to Icon we set a class name to rounded full to make this a circle and then we add an onclick attribute which toggles the theme we add an arrow function here and this will check if theme is equal to a dark then we want to set the theme to light and vice versa then I'm going to copy two icons in here and a span I will explain them in a moment those are both lit react icons pause the video and type this out by hand so those are two icons which will be above each other both have the same width and height of 1.2 Ram both have some animation which just makes it look cool when we toggle between them and here you can see that we use this dark modifier this will apply a Tailwind class only on Dark theme so only on Dark theme the sun icon has a scale of zero in other words it will be hidden and only on the Dark theme the moon icon has a scale of 100 so only one of them will be shown again pause the video type this out by hand and then in a moment we will see how this actually looks ah yeah and this down here is only meant for screen readers because this button doesn't have a text it only contains icons so for accessibility reasons we add this screen reader only text okay then let's use this new theme tockle button in our nafar all we have to do is put it in here we put it above our add note button so to the left of it here we render a theme tockle buttton and Al the logic is already contained inside this component and there it is and now just like that we can toggle between themes the colors are already set up as I explained earlier in the primary and secondary and so on colors in Chi Nui and the way this works again is that the next themes library at a class to the outer HTML tag we can see this when we go to the elements tab here is the HTML of the whole website as you can see the class is set to light when we change this the class is set to dark and this TS T when CSS which colors to use now we could also build this functionality ourselves this is actually not that difficult we wouldn't have to use the next themes Library however the difficult part is to show the correct theme when the page is loaded so when I refresh the page the Dark theme is shown immediately if we build the theme provider ourselves then usually the light theme is shown for a short moment because the theme is stored in local storage and local storage is not aailable server side only client side so it can only be loaded after the page is already hydrated on the client next themes avoids this to be honest I don't fully understand how they do it I just know that they inject some kind of script that loads the value from local storage in advance and then puts it into the HTML before the page is loaded but again I don't fully understand how this works it's pretty complicated this is why I use the next themes Library instead of doing this myself one more thing the clerk popup still has the wrong color because this is not styled by Tailwind but we can fix this clerk also has a dark theme which we can import from at clerk themes which we installed in the beginning and in here we have the stock value what we can then do is we can use our use theme hook to read the current theme this is available in our client components because we wrapped our layout into the theme provider and then in the user button appearance prop we have the Space theme value and and here we check if theme from use theme is equal to dark then we want to use Clerk's Dark theme otherwise we set this to undefined which makes it use the default light theme but this of course needs to be a three equal signs okay let's see now clerk has the correct color as well isn't this cool okay and now finally the base functionality of our note app is done we have a cool theme we can add notes delete notes update notes all the cool stuff and now we want to implement the AI functionality in order to use the cat gbt API you have to create an account on open.com then log in and you get an API key here when I created my account they had a free quter that you got granted when you created a new account like five bucks or 10 bucks or so which lasts forever because these API requests are very cheap you pay like a fraction of a cent for thousands of generated words so yeah create an account here log in get an API key I already have one which we then add to ourn file I'm going to put it right here give it the same name open AI API key and obviously I will delete this API key after recording this tutorial so don't even try using that one it won't work and as I forgot it in which case I might go broke when we have that the next step is to create a pine cone account because this is the vector database where we store our Vector embeddings so click on login or sign up and create an account I already created one with this email address and then you should see this screen and pine con also has a fre tier creating one index is free so we click on create index we have to give this a name again I call this nextjs AI note app then we have to configure this index and the first time I did this I had no idea what I have to enter here Dimensions what the hell is Dimensions Dimensions is simply how big the vector eding is basically how many numbers it contains and I figured this out Simply by Googling I Googled for chbt edings dimensions and I found out that this has 1530 Six Dimensions and we use the coine metric this is also described in the C gbt docs we can't even change anything else and then we click on create index which creates a database we have already installed the necessary dependency in our project now we need the API key we can get it here we copy it into our n file here as well we create a pine cone API key and paste it here and then we add two new files to our lip folder that allow us to interact with these databases so right click on lip the first one is open ai. TS in here we want to get our API key from process. nf. openen AI API ke I stored this in a variable because I want to check if the API key is not defined so if we forgot to set this environment variable then we need to throw an error here so that we don't continue with an undefined key open AI API key is not set as the error message if the API key is defined we create a const open AI and assign this to a new open AI looks like we have to import this manually import open AI from the open AI package then we call this Constructor down here parenthesis C Braes and then here we pass the API key and then we export default this open AI client from this file and then we can use it throughout our app to make requests to open AI then we need a similar file for Pine con since Pine con is another database I want to put this into the database folder we call it Pine con. TS again we create this const API ke which we assign to process. n. Pine concore apiq again we check that this key is defined otherwise we throw an error Pine con API key is not set then we create a pine con client which we do with new Pine con okay again we have to import this manually import the structuring from at Pine con database Pine con and in here is the pine cone Constructor which rec call and configure this time we also have to set an environment which is gcp starter this is somewhere here in the pine con back end indexes yeah here we can see it this is the environment that we have to pass and then the API key here as well and then we need a reference to the index so the index is basically one database our nodejs AI node app is an index this is what we want to export here export const noes index equals Pine cone. index with an uppercase i and the name of the index that's copy it just to be sure so this is the name we copy this and add it here in the string okay and now we are ready to create vector embeddings and store them in Pine Cone and I think I haven't properly explained yet what actually Vector embeddings are so you might have already used the chat gbt API before it's pretty simple to use we send a text request to chat gbt that we write in human readable language so just like we would talk to a normal person and then we get the response from the AI back but as I explained in the very beginning what if we have like a thousand nodes in our database we can't send all of them to chyb because the more text we send over the more we have to pay and also there is a limited context window so before we make our chat gbt request we have to find the relevant notes in our database for a specific user question and Vector embedding is a technique where AI turns a piece of text it can be one sentence it can be a whole document into an array of numbers this array of numbers has these 1536 Dimensions that we entered when we set up our pine cone index so it is an array with 1536 numbers in it and these numbers Define the position in this many dimensional space this is what this graphic here illustrates now why do we do this the numbers in this array actually contain the meaning of the text so let's say this little circle here on the outside is a text that says my favorite activity is coding if we turn this into a vector embedding it would have a position somewhere here in the space now if we have a similar text with similar meaning for example the question what is my favorite activity so both these sentences talk about activity this means they have a very similar meaning so they are close to each other the numbers in each Vector are very close to the numbers of the other Vector but if we have a completely different sentence like on Sunday I at ice cream with a completely different meaning then it will be placed further away from our other two vectors and this allows us to query these vector embeddings by their meaning so if we send a request a question to the chatbot we first search for the notes that are very close in their vectors and then we only send these to jet gbt to work with if this sounds complicated to you don't worry maybe you understand it better once we have actually written the code for it we can create such a Vector embedding simply by making a request to open AI so let's go into our open AIT TS file and add a function here export asnc function get embedding the name of this function is up to you and we will simply send a text to it then we make a request to open AI cons response await open AI do embeddings and they have a really nice JavaScript SDK which is very straightforward to use embeddings do create curly braces and here we have to define the model and there is only one embedding model aailable right now this one here tank embedding adaar 002 and this model is also very cheap so it really costs only a fraction of a scent for large amounts of text and then we have to give it the input which is the text that we pass to to this function and just like this we can create a vector embedding we just have to return it so we create a const embedding we find it in response. data this is an array with only one element so we access the index zero and then here is the actual embedding and as you can see from the typescript type this is an array of numbers because as I explained a vector embeding is a huge array of numbers we will print this to the console in a moment if for some reason the embedding is missing which basically means that the request failed we just throw an error but this should basically never happen error generating embedding but just to make sure we handle this here and then we return the embedding from this function maybe let's also lock the embedding here just so that we can see it embedding then we go into our nodes route file because we want to create an embedding when we create or update a node with the meaning of the node inside this embedding first we go to the bottom and create another reusable function get embedding for note to which we will pass a title string and the content which is of type string or undefined because yeah not every node has content here we want to call and return the get embedding function from the open AI file and then we basically just format this note properly so in here I want to put the title then two line breaks to separate the title and the content so we add a string with two back slns this creates a line break and and then we want to put the content in here but if the content is undefined I want to put an empty string instead this just formats the nodes properly before we embed them okay then we go to the Post Handler right before we create our node we also want to generate the embedding so above node. create we create a const embedding andry call and the waight get embedding for note to which we pass the title and the content this creates this embedding and now we want to store this in our pine cone database so that we can later search for it now here's the thing if the pine corn operation fails for some reason because the database is down then I don't want to create the node in the mongodb either because I don't want to have a note without the vector embedding and vice versa if the mongodb operation failed I don't want to create the entry in Pine Cone we can do this by wrapping these two operations into a transaction for this we use Prisma so what we do is we create another const node and we assign this to a await Prisma do dollar sign transaction this is how we start a transaction in Prisma inside this we pass an Asing function that takes this TX argument TX is also a Prisma client but whatever Prisma operations we do on TX will be part of the transaction and in here we add a function body in case you don't know what a transaction is with a transaction you can do multiple database operations and they will only be applied if all operations succeed if there is an error thrown inside this transaction block then all operations we do on this TX client will be rolled back they will not be applied now we only use Prisma for mongodb right this is connected to our mongodb database so this executes a mongodb transaction this is not in any way related to Pine Cone so pine cone operations are actually not part of this transaction meaning that we can't roll back a pine con transaction we can only roll back a mongodb transaction but we can work around this by first executing the mongodb operation and then the pine corn operation so what we do is we take this part down here cut it out and paste it inside the transaction but we replace this Prisma client on the inside for TX so that it's part of the transaction and still inside the transaction as the second operation we do our Pine corn operation which we do on this SES index that we exported earlier here we can call upsert which creates or updates an entry in Pine Cone and this expects an array of one or more entries in this array we add another pair of curly braces and here we Define the data that we want to store in Pine con each entry has an idea and for the pine con entry we want to use the same idea that the node has this makes it easy later to crib for them the values is the embedding itself this is the embedding up here that we just got from chat GB and then we can also store metadata this is where we put the user ID of the user who created this node and later we can filter by this metadata so that we only find Vector embeddings for this particular user and then at the end of this transaction we return the node so this is this node and by returning it from the transaction it will be put into this return value and we can return it from this API endpoint just like before now as I explained Pine Conn is not actually part of the transaction but since we execute Pine Conn second this will still work because if we execute the mongodb operation successfully but Pine con fails this will throw an error which will cause the transaction to be rolled back which will undo this create operation on mongodb however if the mongod Deb operation fails then we will not even get to the Pine corn operation this is why the order of these operations is important you must not put the pine corn operation above the mongod DB operation then this transaction will not work okay let's save this and try this out first of all I want to delete all the existing nodes because they don't have a vector embedding and pine cone yet right so let's get rid of them going to delete all of them one by one and start with a blank slate make sure you have saved your route file and now when we create a new node it should create a vector embedding which we should print to the console and then also store in Pine Cone let's already put some information in here um I'm going to call this my favorites my favorite color blue my favorite car BMW my favorite hobby coding just so that we have something that we can search for later submit there is the vector embedding this is actually abbreviated here but this contains yeah 1536 of these little numbers which Define a point in the vector space which contains the meaning of this note isn't this absolutely futuristic and now this should also be stored in Pine Cone so let's go back let's refresh the back end and there it is the user ID is stored in the metadata we can filter by this later the ID is the ID of the note and values contains the actual Vector values nice now let's do the same when we update a note so we scroll down to our put route again we go above our mongodb operation create an embedding here just like before await get embedding from for note to which we pass the title and the content and again we need a transaction here so const updated note equals array Prisma transaction to which again we pass this async function with the TX client again we want to move this in here change this Prisma client to TX and then we want to make the same Pine cor upsert call a wait noes index. abser parentheses scrap records curly Braes we pass the idea which we already extracted here that's the IDE of the not we are updating the values again is the embedding and the metadata is the the user ID and then at the end of the transaction we return the updated note this is necessary because if we change the contents of a note we also might change its meaning right so we have to create a new Vector embedding and finally when we delete a note we have to delete the vector entry in Pine con again we do this in a transaction One Last Time Prisma transaction async TX in here we put this line call it on the TX client and then we call a wait nodes index. delete one and to this we simply pass the idea of the notary when to delete okay let's test the delete functionality I create a new node with some gibberish just to have a new entry in Pine Cone let's refresh it there's our second entry and when we deleted there should only be one entry left in Pine Cone and voila only one entry and since we execute all these operations in transactions the pine cone and the mongodb database will always be in sync there should be no way for them to get out of sync okay and just like this we implemented Vector embeddings into our app now the last step is to create our AI chatboard use the vector embeddings to find the relevant documents for user query and then send them to chat gbt to get an answer this is the most exciting part of this tutorial the payoff basically so make sure to watch it all the way to the end okay now I want to add a little chat box that appears in the bottom right corner of the screen when we open it and that allows us to chat with the AI so let's create a new component for this let's put it into the components folder I'm going to call it AI chatbox ttsx let's export a component function export default function AI chat box and the component will also take props AI chatbox props the color of the chat box will decide if the box is shown or not so open Boolean and an onose callback and then we pass these props to the component itself as usual and as I already explained in the beginning we will use the Vel AIS SDK this is a package created by the inventors of nextjs so it works perfectly together with nextjs but also with some other Frameworks as you can see here like KNX and solid JS and it solves two problems for one it manages the chat messages and all of this for us now this is something we could easily do ourselves but it's very convenient to use this use chat hook instead the more important feature is that this helps us implement this response streaming where we get the AI response sent letter by letter and this doesn't only look cool it makes the app feel much faster because we don't have to wait until the whole AI response has been generated which can take quite a while for a large texts instead we get streamed the response as soon as the the first letters are ready basically basically and the Vel AI SDK makes implementing the streaming functionality very easy this is why we will use it here okay we have already installed the package now we want to call this use chat hook up here so we call use not use cat use chat looks like we have to import this manually import curly braces and we should find it in this AI package slash react and in here should be use chat we want to call this hook here and again this is coming from the Basel AI SDK and in this destructuring declaration we have different values for example the messages because again this hook handles the chat messages for us so it keeps the state it automatically makes the request to our endpoint and so on for now let's just destructure a bunch of different values that we will use we will use the messages the input which is what we type into the input field of our chat box handle input change Updates this input handle submit to submit the values and send them to chat gbt with set messages we can change these messages here manually if we want like making them empty again to clear our chat this loading should be self-explaining and error as well there are more values you can destructure and you can also do some configuration in this used chat hook but we don't need this right now then we want to return a diff that contains the chat box let's add some styling to the stuff we want to display the chat box in the bottom right corner so we add this bottom zero attribute and right zero we position it in the bottom right corner we add a zindex of 10 Z index means that this will be shown on top of other elements that have a lower zindex because we always want to show this chat box on top right we set it to width full but we set a Max width which we hard code like this with scrap brackets to 500 pixels so on small screens below 500 pixels it will take up the full width of the screen whatever is available on large screens it will be at most 500 pixels wide we add some padding with P1 and yeah we position it on the right side of the screen which looks good on small screens so it will be positioned right here at the bottom right but if we have a large screen I want to position it a bit more towards the middle I don't want to put it all the way to the right Edge so we use a responsive Tailwind modifier on large screens so XL column we set right 36 so we overwrite this right Zer pixel value with a larger value but only on large screens you will see how this looks in a moment now what we do is we take this whole string we highlight it like this surround it with curly braces then we surround it with parentheses and in front of it we want to call this CN function which is coming from the lips folder this function was created earlier when we initialized chat CN and it's basically just a way to combine different TN classes and have them overwrite each other intelligently normally this is needed so that you can overwrite these hardcoded values from the outside for example the button has BG primary by default but we might want to pass a different color via the class name prop to it then the end takes care that the value we passed from the outside overrides the value in here here this is what this is for but we can also use it to pass class names conditionally this is why I use it here so at the end of this function we add a comma right after the string and then we can simply add more classes and I put it separately because here I want to add an expression and this way we keep this more readable if open is true so if the chat box is opened then I want to set the position to fixed because only then this bottom and right zero is applied we want to set a fixed position if not we want to set display none which hides this chat box maybe for now let's put some text in here chat box and render it on the screen to see it while we are building it I want to create a separate component for the button that opens and closes the chat box so let's create another component in the components folder let's call it AI chat button. TSX export default function so at the top this will contain a state chat box open and set chat box open and we call use State and pass fals as the default value and then we return a component we put this into a fragment because in here we want to render our AI chat box we pass the open value which is the chat box open State and on close we pass an aror function which sets chat box open back to fals and then above in this fragment we put a shed c n button which will say AI chat and we also have to close this AI chat box component next to the text in the button I want to put a lit react icon the spot icon set the size to tr and add a margin right and then we add an onclick Handler to this button which will call set chat box open and set this to true and then we can use this AI chat button in different places I want to pull it into the N bar I want to put it right after the add note buttton here so the add note button is this one so below we put our AI chat button but all the state and all the logic is contained in these two components so when we click this button we see something down here so let's finish setting up the chat box okay let's remove this text here I want to put a button but with a lowercase beer this is not a style chaty end button it's just a raw HTML button without any styling this will be the close button of this chat here I want to render this x Circle icon which we give a size of 30 there it is here at the bottom and then on the button wrapper we add an onclick Handler which will call the onclose Callback and some styling a small margin bottom we set MS Auto and block which moves this to the right side of this chat box component now we could also add an onclick attribute directly to this x Circle icon they support onclick handlers but for accessibility it's better to wrap this into a button because this way we can highlight this by tapping through our components as you can see now this is highlighted this only works if we wrap this into a button but it's important for accessibility okay and below this close button is the actual chat box itself right this diff on the out side is basically just the repper that contains these two components so in here we put another diff add some styling to this diff as well we make this a flex box we set a fixed height of 600 pixels like this we also make this a flex call so all the elements will be aligned below each other vertically we set this to round it which will give this rounded Corners maybe to see what we're actually doing here let's set a background color first and let's give it a border with this border class which creates a border of one pixel and there it is now we have this box with rounded Corners let's also add the shadow XL which makes it more distinguishable from the background inside this diff we put another diff which will contain the chat messages but for now we just write a text in here messages so that we see where this is aligned and we set a class name on this inertive of H full so this will take up 100% of the available height okay and below this messages div we put a form which will only contain a single input field but this time we don't need a shed the end form we use a normal HTML lowercase form for one because we don't need input validation but also because the form is handled for us by the Basel AI SDK this is where we can use a perim ative form we pass the onsubmit function to which we pass the handle submit function that we get from use chat again AL the logic is handled for us by this Library we style it with class names we set the margin of three and we make it a flex box and give it a small Gap because this will contain two elements the input field and the submit button so for the input we use the uppercase ey from our components folder simply because this one is already styled and it looks better we give it a self closing tag this is managed by the use chat hook so for the value we pass input for on change we pass handle input change which both come from the use chat hook and we can set a placeholder say something there it is with the margin around it looks good and below the input still inside the form we also put the submit button for which again we want to use a styled button of type submit because by setting the type to submit we execute this onsubmit callback here and this will just say zent and there it is looks nice so now we have our chat boox shell we can close it and open it it's placed on the right side on smaller screens it will take up the full available width whatever is left and on large screens it's placed more towards the middle and not all the way on the right side which I think looks better okay and the next step is to set up our backend route where we actually make the request to the chat gbt API and get our chat messages now the way this Vel AI SDK works is that this will automatically make a request to a route on our own back end by default this makes a request to a/ API SL chat and it sends the chat messages there so we can create this route read these messages and then make the request to chat gbt this endpoint can be customized if you want to give it a different URL you can do this in here I think this API key is the one where you can change the endpoint but we keep the default one because this is fine so this means we need a new route in our API folder so let's right click new file chat SL route. TS to create this folder and the route file this needs a single post endpoint so export Asing function post as usual we get the request we wrap this into a TR catch block and I want to handle errors the same as over in our noes app end points lock it to the console and return an error response always the same then we need to read the body of this request which again we get with await rec. body and and as I explained in this body are the messages that the Vel AI SDK automatically sends to this route yeah this should be rec. Json and not body so we create a const messages and we assign it to a body. messages but typescript doesn't know the type of this so this is any right now to get type safety we can set the type ourselves for this we use this chat completion message type which is an import from open Ai and this is an array of these messages this is the type that the open AI API expects it contains the role of whoever sent the chat message and the text of the chat message okay and we will send these chat messages to the chat gbt API but I want to truncate these messages I only want to send over the last six messages for one to save tokens and make our request that's cheaper but also because we retrieve the notes relevant to our current chat history with our Vector embedding technique so that we can pass them to chat gbt but if we have a lot of messages in this array then the vector embedding will not be very exact right because there might be a lot of different topics we talked about with the AI so there are a lot of different meanings in our chat history and then the vector embedding doesn't know what to search for so we narrow this down to the last six messages so let's create the messages trunk caded variable and to get the last six elements of this array we call messages. slice and pass minus 6 to it so now we have the last six messages which contains user messages and AI answers so the chat history and now we want to turn this into a vector embedding so that we can search for the relevant notes that fit to the current chat history and the query of the user so let's create a con embedding we call our get embedding function which we created earlier in the open AI file this simply takes a raw text and to it we pass our messages trated which is an array we have to turn this into a single string which we can do with the map function here we get past each message we want to get the content out of these messages and then after the map function we want to join all of these messages with a line break between them so we pass a string back sln so this way we simply take the text of each message and we join them together into a one big string with a line break between them and then we take this chat history and turn this into a vector Ting and the resulting string looks somewhat like this the user might have sent a message hey what's my wifi password the AI response your WiFi password is XYZ thank you what is my phone pin and then the AI response again and this is what we turn into a vector ilding this whole string of chat messages this is a very simple approach it works quite well from my experience but there are more sophisticated approaches to create these Vector embeddings for example we could have another step where we send our chat history to chat gbt first and ask it to turn it into a newer question to create a more exact Vector embedding but in this tutorial I want to keep this simple so that you can get started with this topic and from my experience this approach works so we just turn this whole chat area into a vector embedding and compare it with the vectors of our noes by the way I'm currently building a little startup that's my site project smart diary. you can check it out if you want it's an AI power journaling app it uses the exact same Tex stck as I'm teaching you right now and there I use the same approach when it comes to Vector embedding I take the last six messages turn them into a vector and then I compare them with the vectors of the Diary entries and this works quite well so yeah check out smart diary. would be really happy if you try it out but yeah there are more sophistic indicated approaches and I think this is where a tool like L chain comes into play this is another library that makes it easier to combine different AI tools and have more sophisticated algorithms we don't use Leng chain here because frankly we don't need it but if you want to see more advanced AI tutorials in the future where I use a library like length chain then please leave a comment under the video and tell me that you want to see more of these tutorials okay let's continue with our code we also need the user idea of the user who's making this request which as usual we get from the off function and then it's finally time to make a query to Pine Cone to find the nodes that have an embedding close to the embedding of our chat history which again means that their meaning is similar so if in the chat we talked about passwords then it will look for notes where the text also talks about passwords so let's create a const vector query response and we call a weight nodes index we have used this variable before that's what we export from the pine con TS file and using it is pretty simple we can call quer parentheses curly Braes we have to pass a vector to it which is the embedding we just created we have to pass this topk value which simply means how many results we want to return so the higher this value is is the more noes you return and four is a good value so we have four nodes that resent to CH gbt if you make this value higher then it's more likely that you actually find the information you're looking for but you also send more data to C gbt which makes it a bit more expensive so four would be a reasonable value in my smart diary app I actually use 30 so I use a quite high value this is up to you you can experiment with this now to see that this actually works for now I want to set this to one to only read return a single result and then we can set a filter and we want a filter for the user idea because of course we only want to find documents that belong to this particular user okay and this way we fetch the vector embeddings from Pine Cone but remember the vector embeddings don't contain the nodes themselves just the ID of the node so we also have to make a request to mongod so let's create a const relevant nodes we call await Prisma do node. find menu parentheses C bra we add a wear filter ID column we want to compare the idea then we write in column Vector query response do matches which are the results we got back from Pine Cone and we want to map these matches we take each map and we want to map them to the idea so match. idea so this here creates an array of the ideas of the results we get back from Pine Cone and with this operator we only return notes from mongod be that have IDs that are included in this array because remember our pinec cor entries have the same IDs as the noes in mongodb okay below let's print the relevant nodes to the console so that we see that this actually works so this will say relevant noes found column and after a comma we pass relevant notes which right now should only be a single one but later this can be multiple ones and then we make the request to chat gbt itself and we send the relevant notes over to it and if you have never used the cat gbt API the way this works is that you basically talk to it like you would talk to a human since it's a large language model it understands natural language so what we can do is we can start with a system message that gives the AI instructions again we set the type to this chat completion message which we already used above here for the messages array but this is a single message this will give us autoc completion because each chat completion message needs a role for which we have different options assistant is the a system is what we can use to give instructions and the user is the user function is something different you don't have to worry about this right now we want to send a system message which we can use for instructions and each message needs a Content which is the message itself this is another string so here we can give the AI instructions you are an intelligent no taking app you usually start with something like this you answer the users question based on their existing nodes period space because I want to append more text but I want to put this into a separate line just for readability so with a plus we can append another string then we say the relevant notes for this care R column we add a back sln to make another line break and we append another string and then we want to pass the relevant nodes to it so we take the relevant noes which is an array recal do map on it to turn it into a string we take each note and then we create a back Tex string and then we just have to format this properly because again the AI basically reads like a human so we have to make it readable for it so this will say title column then after a space we interpolate node. tile then we make two back slash NS to add two line breaks content column since content is multi-line I want to add another line break back sln and then dollar sign curly braces here we put node. content so this creates a text that looks something like this title passwords and then content column new line and then the content of the note below it like this but we also have to call do join on this to turn this array into a single string again we join this with two line breaks so if we have multiple nodes then they will all be put in to this prompt okay and then we make the request to a chat gbt so let's create a const response equals a wait open AI from the lip folder. chat. completions do create remember earlier we used the embedding endpoint now we use the completions endpoint which is basically the chat endpoint parentheses curly braces here we Define the model that we want to use here we have different options available gbt 4 is the most powerful one at the moment but the cheapest one that is still very good is gbt 3.5 turbo that's chat gbt so this is the best choice for most apps because it's much cheaper than gbt 4 at the moment comma we set stream to True which enables this response streaming and then we pass the messages to it those are the chat messages from up here plus our system message with the instructions so we create an array we put the system message first to start with this comma and then we spread our messages truncated to also send the last six chat messages together with these instructions this is how you make a request to Jet gbt and now we just have to return this to the front end for this we use the Vel streaming helpers so we create a con stream we use this open AI stream looks like we have to import this manually so let's go at the top we want to import two values from the AI package one is the open AI stream and the streaming text response and how to use them is described in the Vel AIS SDK documentation but it's pretty simple we take this open AI stream Constructor and has the response to it and then below we return a new streaming text response to which we pass the stream and that's it and the use chat hook we use on the front end already knows how to read the stream and the request to this endpoint is automatically done by the use chat hook so all we have to do now is to render these messages in the UI so let's do this below let's create a simple component to style a single text message so we create a function called chat message this we expect a message prop and we declare the type like this if there's only a single value in there then I sometimes don't create an interface for this because it's shorter to just declared inline so we write curly braces for the type message column and the type of this this message from the AI package because this is a single text message and we can destructure this message object from it we need the rle and the content because this is what we want to put into the layout and then for now again let's keep this very simple we will style this later for now I just want to render a diff that contains the r and the content this won't look great yet but it will be sufficient for now let's also add a margin bottom to each message and then let's render these messages here where right now we only have this messages text instead we put an expression with cly Braes we want to take the messages and map each message into one of our chat message components to which we have to pass the message itself and the key as usual because we maap this here into a list each message has a unique idea that we can use here okay let's save this and see if our AI has life in it high user and after a moment we get streamed our response isn't this amazing now we still have to add proper styling and we will also add a loading State while the response is generating and before streaming has started but for now this is already pretty sick and this AI should know about our notes because we passed the relevant notes to the request let's create a few more notes so that we can find information in them so I added some random notes and remember we seted topk to one so it should only find one relevant note when we make a chat request so let's clear the terminal and try this out so for example I could ask it what is my Netflix password so we open thei chat I also want to refresh the screen to clear the chat we will add the clear chat button later what is my Netflix password this is the vector embeding and this is the node it found as you can see it found the relevant note now since top K is only one if we ask about something different now from from a different Noe then it might not find the information because it will only fetch one note and in the chat history we talk about the passwords and then about something else so let's try a movie from the watch list this will probably not work give me a random movie from my watch list but again the chat history is still polluted with our first question so this will probably not work because it still finds the password node and to solve this problem we have to increase the top K value because now we allow the AI to read four different notes instead of just one so now if I ask this again it should work can you try again and the cool thing about these large language models is that you can talk to them like with a human if I ask can you try again it will recognize that I'm still talking about my previous question and now with a larger top K value we get four notes and it gave me a random movie from my watch list Bobby which is on the Note isn't this cool and now let's finish the styling of our chat box so that it looks good I want to show the user profile picture next to the user messages in order to load this image from the clerk server we first have to add this URL to the next config JS file so let's go in here and into this next config block here we add images column curly braces and domains looks like this is deprecated so I guess we have to use remote patterns now this should be an array with objects in there so scrap brackets curly braces here we set the host name to IMG do clerk.com and I'm doing this from the top of my head right now because I prepared the code for for this domains field but this has been deprecated in nextjs 14 apparently and we have to use remote patterns instead let's save this for now we have to restart our development server in order for the changes to take effect so we stop the execution and run npm run def again and then let's go into our AI chatbox component and then go down into the chat message component above the return statement first of all I want to get a reference to the currently logged in user to get the profile picture we can get it via this use user Hook from clock and in here we can find the user with all the user information this works because our whole app is wrapped into this cler provider which is a context provider and with use user we access the values in this context provider then I want to create the Boolean is AI message and we assign this to a roll is equal to a existant so that we don't have to repeat this here over and over again we now have this Boolean that tells us if this is a user message or an AI message okay and then I want to style this outer diff here again re wrap this mb3 string into curly braces parentheses and use this CN function here again I want to add some more classes I want to make this a flex box with item Center and as the second value to this function after the string we want to pass some logic again and again I use this CN function for better readability because here I want to check if is AI message is true then we want to apply some classes and if not we want to apply some other classes if this is in a IMS M I want to use justify start to put this on the left side of the chat and I want to give it a margin end of five if this is a user message I want to do the opposite I want to use justify end to put this on the right side and a margin start of five now since we restarted the development server we have to refresh the page to load this again let's create another AI chat uh I'm going to ask about my favorite car what is my favorite car and it answers the AI is now aligned left and the user message is aligned right but of course it doesn't look great yet let's remove these two diffs with the role and the message for now and set this up properly first of all we put an expression here again we check if AI message is true in this case I want to render this bot icon again to which we add the classes margin right of tour and Shrink zero shrink zero takes care that this icon doesn't shrink if the text gets too big then below we put a paragraph tag for the message itself so this will contain the content and then we style this as well we use an expression here because we want to call this the N function in the first string we add wh space preline again this is to maintain the formatting of the message rounded MD to give it some rounded Corners each message should also have a border of one pixel with PX3 we can set horizontal padding so left and right side and with py2 T we set a different vertical padding and then again after a comma I want to put an expression into the CN function again we check if it's in AI message then I want to set the background to BG background colon otherwise I want to use BG primary which is our black color or white on Dark theme and then we also have to change the text color and set this to a text primary foreground as you can see this already looks much better but we are not quite done yet below the content so on the right side I also want to put the image of the user of course we only render this if this is not an AI message and then after two ENT signs we also have to check if user. image URL and user could be undefined so it adds the save call Operator only if the user has an image URL then we want to render a next image the source of the image is the image URL which is now defined we set the alt text to a user image and we set the width and height to 40 pixels and then let's add the class names ml2 for some margin on the left and round it full to make this a circle uh it doesn't look right yet yeah I think this is because the image is wider than it is higher so it loads this in this small size and what we could do instead is we could actually load this a bit larger than we needed 100 and then set the size with CSS so let's try try setting a width of 10 a height of 10 and use object fit cover with the object cover class to show this properly this looks better because now it's loaded in a bigger size so that it actually fits in here but if the text gets too big we also need a scroll bar here let's see please write down some long lurm ipso dummy text I want this to get bigger than the aable height please add some more and eventually it will not fit in here and the input field gets pushed out of the screen to fix this we go up here to the diff that contains the messages with the height full first of all I want to add some margin top I also want to set some horizontal padding looks better and then we add the scroll bar with overflow y Auto and now we have a scroll bar if the chat gets too big nice looks very cool our chat box still requires some Polish for example I want this to automatically scroll to the bottom when a new message arrives otherwise if I ask it some more like thank you this will be stuck at the current scrolling position which of course is not nice and I also want to add States for when the chat is empty or when there is an error and I want to add a clear chat button so let's do that next res scroll up below our used chat call here we want to create two revs the first one is called input rev and we do this in react RFS use ref hook we set the type between angle brackets HTM L input element and we initialize this with null you will see what this is for in a moment the second is the scroll ref again we call use ref the type this time is HTML diff element and again we pass null as the initial value ref allows us to access an HTML elements directly we can use this for example to set focus on the input field or scroll down in this chat history div when it becomes too big we can do this via refs and we have to set these refs on the elements that we want to access so the scroll ref is for this diff here that contains the chat messages after the class names we can pass a ref prop and P the scroll ref here and the input ref is the input field which is down here let's assign the ref here as well oops like this input ref then we scroll up again we go below our refs and we create two use effects the first use effect will be responsible for scrolling down whenever the messages change so we pass the messages array as a dependency so this use effect will be executed whenever messages changes so for example when we get a new message then we want to scroll down first we have to check if scroll ref. current has a value because this is nullable and we can scroll down with scroll ref. current do scroll top equals and we assign this to a scroll ref do current. scroll height I got this of course from stack Overflow this is just just how we scroll down in such a vertical scrolling R this should now already work so let's write something else I'm just going to write thank you again and it still didn't work I might have to refresh the page to load this again maybe I have restarted my development server so let's try this again let's start with higher please give me some lurm ipom text I can't do this because okay apparently it wants me to change the way I ask it yes please generate some the behavor of this AI always depends on the system message we gave it we told it that it's in intelligent not taking app so when we ask it for something like a Lauren ipom text it acts like a not teing app and not as a general a I this is where we get this real behavior um one please one paragraph and now yeah it Scrolls down automatically as you can see so it didn't work before because I had to refresh the page but now it Scrolls down and it's even works with streaming and I created this input ref because when we open the chat box I want to put the focus on this input field automatically which we do with another use effect this use effect checks if open is true and it will be executed when ever open changes and if open is true we want to take the input rev again access current and then we can call focus on it so now we should automatically put the cursor into the AI chat when we open this input field which is just a bit better for user experience next I want to add a button to clear the chat history we put it to the left side of the input field still inside the form right here we put an uppercase B button and this button will not contain text but this trash icon there it is but we change the styling of this button first of all I want to give it a title that says clear chat the title is what appears when we hover over it in this yeah little popup here when we hover over it this is not a react component this is rendered by I guess the browser or the operating system but this is the easiest way to get the hover tool tip onto an element we set the variant to outline we set the size to Icon which makes it a bit narrow but it shouldn't be that small it it gets shrunk because the input field pushes it away and again we can fix this with this shrink zero class that we used before now it has the appropriate size we set the type to button because it's still inside this form and when we click this it should not submit our form and we avoid this by setting the type to button and we give it an onclick Handler to which we pass an error function which call set messages and passes an empty array so set messages is part of the used chat hook and this way we can make changes to the array directly like making it empty in this case let's try this out High yeah we will add a loading state in a moment and now we can clear this chat history and since this array is empty these previous messages will also not be sent to the back end with our next request because they don't exist anymore okay then the last thing I want to add is a loading State an aror State and an empty state for this chat we do this below our messages map call here still inside the same div before the chat message starts streaming we still have some waiting time right and I want to show a message while this is loading we have this is loading value up here that we can use while the response is loading but but this loading is also true while streaming is in progress but once streaming starts I want to hide the loading message and we can check for this with another Boolean let's put it here above the return statement we create a const last message is user which will only be true until the AI has started streaming and we check this by taking the messages array we want to access the last element which we can do with messages. length minus one here we want to access the role and we might have to use the save call Operator here because this could be empty right so add a question mark here if this role is user then we know that the last message is from the user then below the map car right here we add another expression where we check if is loading is true and the last message is from the user then we want to render a loading message and for this we will render a chat message which is our own component this expects the message object right and we can create one ourselves with curly braces here we set the role to assistant and the content to thinking or whatever you want to use as the loading message now this shows an error because a message also contains an idea now we could invent an idea but we don't need the ID down here in our chat message we only need the role and the content right so we can tell typescript we expect a message but we don't care about the ID field for this typescript has a utility class which we add before this message type it's called pick we don't have to import this this just works out of the box we use this pick and we surround messages in angle brackets and after message we add a comma and then we add a string and we can tell typescript what fields we want we want roll vertical bar and another string content this way we tell typescripts we want a message but we only need the role and the content and nothing else not the ID now this has the same effect as just adding the role and the content separately as separate props but this way we still get this nice Auto completion where we only have different roles that we can pick from so now this doesn't require an ID anymore and our error up here is gone so let's see if this works higher it should show thinking for a short moment until it starts streaming thinking and then it starts streaming much better now the user sees that something is happening similarly if error is true which again we get from the used chat hook then I want to render an error message so we put another expression below that checks if error has a value then we want to render a chat message as well to which again we pass a message as an object the role is again the assistant and the content will say something went wrong please try again without types to try this out we can return an error response from our back end let's try this out here at the top throw error of course we will remove this in a moment Bazinga I'm not sure if the Vel AI SDK handles 500 responses as errors but I would assume that it does so let's save everything and try this out hi again yeah something went wrong please try again and it also removes the last user message which I guess makes sense so this works as well let's remove this error here and lastly I want to add an empty state so so that we show something when the chat is empty we might have to refresh this so once again below another expression I want to show this empty state only if there is no error and if messages. length is equal to zero then we render another div which we Style with flex Age full item Center justify Center and a gap of three and in here I want to put a spot icon again with its default size and some text that says ask the AI a question about your notes so and there's our EMP State and this disappears as soon as we have messages thinking and when I clear the chat we see the empty State again nice congratulations for building your own AI chatbot again I think this is one of the most important skills to have going forward because there are many job opportunities coming up with this technology and almost all apps in the future will have some kind of AI integration often chat gbt I didn't add the deployment to this tutorial because I have covered this before in other tutorials if you want to deploy this project then check the last part of my nextjs e-commerce tutorial I will put a link to the video in the top right corner there you can go to the last part the deployment part and there I show you how to deploy this next JS project with Prisma and everything but it's very easier and if you want to see more AI tutorials like this one in the future maybe with more advanced features maybe using something like Lang chain then let me know in the comments below because only if you actually like these videos I have a reason to make more of them if you like this video please leave a like if you haven't subscribed to the channel yet please do so and then I wish you a nice rest of the day happy coding take care
Info
Channel: Coding in Flow
Views: 11,075
Rating: undefined out of 5
Keywords: next.js 14, nextjs 14, next js 14 tutorial, next js chatgpt, nextjs chatgpt, next js chatgpt api, next js chat gpt clone, next js chatbot, vercel ai sdk, vercel ai sdk docs, vercel ai sdk usechat, vercel ai sdk functions, vercel ai sdk chatbot, vercel ai sdk streaming, next js ai streaming, nextjs streaming, streamingtextresponse, vector embeddings openai, vector embeddings explained, vector embeddings chatgpt, next js pinecone, next js app router, openai, nextjs project
Id: mkJbEP5GeRA
Channel Id: undefined
Length: 234min 18sec (14058 seconds)
Published: Wed Nov 01 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.