In this chapter, we're going to enable AI tool calling, enabling our AI to escalate the conversation to a human operator or to resolve it themselves. Before we do that, we're going to first enable manual status change from the operator side. So let's go ahead and go inside of our packages backend convex private conversations. And in here in the previous chapter in our get one query we do an invalid if check here. So once I obtain the contact session I need to check if the contact session doesn't exist and throw the error.
There is a chance that you did this correctly. This is what CodeRabbit told me in the previous chapter to fix. So that's what I'm fixing now. Basically in this get one query. And now let's go ahead and let's develop update status mutation so make sure you have imported mutation from generated server and then in here let's go ahead and let's define all the arguments that are available.
But before we do that let's just define a handler so we get rid of the errors so it's easier to look at. And then let's go ahead and add conversation id which is a type of VID conversations and then status which is a union of these three values. They need to match exactly what you have in your schema for the conversations table. So just make sure that this is exactly the same. You can even copy it from here and then paste it here.
Now let's go ahead and let's get the context and the arguments in the handler and let's go ahead and first do the identity and organization check by simply copying it from the get one query and let's go ahead and paste it here. It should be identical. Unauthorized if identity wasn't found and unauthorized if the organization wasn't found. Now let's go ahead and as always let's check if the conversation that we are trying to modify exists in the first place. In case conversation does not exist let's go ahead and throw a convex error.
I think we can borrow that from below as well. And while we are here you already guessed it let's check if the conversation organization id matches we can copy this as well. And let's throw an error if it doesn't match. There we go. And now let's go ahead and simply do await context.database.patch arguments.conversationId let's pass in the status arguments.status.
That's it. That's all we need for manual update status. Now let's go ahead and let's implement a component that will do that. So let's go ahead and first implement a little popover component so that when we actually hover in here we're going to create that button for the operator to click that. But since it's not too intuitive because it's actually going to display the current state.
So if it's if it is unresolved the button will display unresolved. So it won't exactly be 100% clear what will happen if I click on that. So we're going to implement a little popover or a hint component that will basically be this. Like this. When you hover over it will tell you what will happen so that's what I want to do first let's go ahead inside of our packages let's go inside of UI, source, components and let's implement hint.tsx Let's go ahead and let's import everything from tooltip.
You already have tooltip when we added all Shazian components and let's mark this as use client. Let's create an interface hint props and inside of here let's create the props. So these are going to be the children which is a required react node text which is a required string side which is optional and it can be top right bottom or left a line which is optional and it can be start center or end. These values actually match the tooltip pretty well. So if you go ahead inside of here, not sure exactly where, Okay, they are internal, I think, from Radix itself, so they're not actually exportable.
Maybe there are through Radix, but let's just write them manually. Now let's export const hint component. And let's assign hint props here. And now we can destruct all the props and we can define the side and the align to be top and center by default. And now inside of here we're just going to follow the tooltip composition.
So tooltip provider. Tooltip inside. Tooltip trigger. Which is going to have as child property and children rendered inside. And inside the tooltip, but below the tooltip trigger, we're going to add tooltip content and instead of a paragraph we're going to render the text.
As simple as that. Now let's go ahead and let's go inside of our apps web and let's go inside of modules dashboard UI components and inside of here I want to go ahead and I want to develop the status button So I will do conversation status button dot TSX. And let's go ahead and do export const conversation status button. Let's go ahead and quickly define the props. So that's going to be status, which is a type of document.
Whoops, not from here. Document from workspace back and generated data model, select conversations, and then select the status prop. And the other one is going to be on click, which will just be an empty void and now let's go ahead and do the following if status is equal to resolved whoops, I have to destructure all of this don't I, so status and on click there we go, And now this should be not crossed out. Let's go ahead and let's return a hint which we just developed so we can import it. Import hint from workspace UI components hint.
And let's go ahead and give it text mark as unresolved and inside add a button from workspace ui components button render the check icon from Lucid React and write resolved. Go ahead and give this button an on click of on click size of small and variant of tertiary. Now we don't have tertiary. So Let's go ahead and quickly develop it. So you can command click inside of this button or control click if you're on Windows.
Or you can manually go inside of packages, UA source components and find the button. In here after transparent, Let's add tertiary, BG, gradient to bottom, from, and now I'm just going to use a very specific shade of green that I like, this one, to, and now I'm just using a darker shade of green here. Like this. Text will be white. Hover will go to, my apologies, hover will change the to value to be this again but with a 90% opacity.
And again, let's add hover text white. And now let's go ahead and let's copy this. And This one will be warning. This one will be from yellow 500 to let's change the specific shade of orange BE8B00. And same thing here.
Just a 90% opacity. Like that. And actually we don't need hover white I think. I think we can remove it from both places. There we go.
So they are identical. But in here I found this. Okay. I haven't found the closest color inside of tailwind. There probably is some.
I just like to be specific with my colors for some weird reason. Feel free to change this to normal tailwind colors. I just like this. All right. Now we have Territory and Warning.
Let's go inside of Conversation Status button here and we should no longer have any errors here. And now let's go ahead and copy the if here. This one will be escalated and return mark as resolved. Go ahead and add arrow up icon and this will be escalated and change this to warning. And then finally we have a default return and that's basically going to be the unresolved status.
So hint will be mark as escalated, variant will be destructive and we're going to use arrow right icon and unresolved. All icons are from Lucid React. So what's going on here? If we are unresolved and you click on this button, we're going to mark this as unresolved. If we are escalated, we're going to mark it as resolved.
If we are unresolved, we're going to mark it as escalated. This won't make sense until you actually see this in action, but it's actually quite a logical toggle loop. So Let's actually use it in action here. And I actually think that we can call this from here. I keep thinking that I need to pass the onClick here.
You know what? Okay. Let's make this component serve for one purpose and one purpose only, and that's to display things. And let's keep the logic, this on click button inside of conversation ID view. Let's do it like that.
So let's now focus on the conversation ID view and below on submit. Let's go ahead and let's add the update status function. So I'm going to do const update status to be use mutation API private conversations update status and let's do update conversation status and then I'm going to do const handle total status. Let's go ahead and mark this as an asynchronous method. And in here, the first thing I'm going to check if we don't have the conversation and let's early return.
Otherwise, let's go ahead and let's define the new status here to be a type of unresolved, resolved or escalated but not defined. And then let's go ahead and check. If conversation status is unresolved, new status will be escalated. Else if conversation status is escalated, new status is going to be resolved. Else, new status will be unresolved.
So what's the flow here? We are going to cycle through states. From unresolved, user will be able to click and this will then become escalated. From escalated, it can be resolved and from resolved, it can go back inside of unresolved. So if the button says unresolved, you click on it and it will be escalated.
When you click on escalated, it will be resolved and if you want to, you can then reset the flow. And now let's open a try here. So await update conversation status, conversation ID, and status new status. And let's go inside of catch error, and let's just console error, error. Like this, There we go.
So now we have that. Let's go inside of the header and after this button let's add status conversation status button And let's go ahead and pass it on click here to be handle toggle status and status conversation status. I can't decide between status and status. So I'm just using both of them. My apologies.
And let's go ahead and how about we do if conversation exists, then do this. Maybe that's better. Yeah, it's okay. Let's do it like that. All right, and let's turn this into a Boolean.
Okay. So now, if you do TurboDev and head to your web, so localhost 3000 in my case, here in this corner you should have a button that will allow you to cycle through different states. So you can see that this one is unresolved and I can mark it as escalated. This one is resolved and I can mark it as unresolved. This one is escalated and I can mark it as unresolved.
This one is escalated and I can mark it as resolved. So let's go through the cycle. There we go. Perfect. You can see how this goes through the cycles.
And Once it is resolved, you will see that I cannot talk back because this conversation has been resolved. We should also disable the enhance button if the conversation is resolved. And one more thing I want to do, I want to add disabled prop here, disabled optional Boolean And let's simply add to each of these buttons, disabled, disabled. Like that. And then Let me go ahead and add const isUpdatingStatus setIsUpdatingStatus useState false.
Let's import useState from React. Make sure you've added it. I'm going to go ahead and set this to be true here. And then in the finally, set, whoops, set is updating status will be false. And let's go ahead and pass here disabled if set is updating status.
So we can't just spam it. Whoops, is updating status. So now when you click here, it should be disabled until the action happens. And you can see how cool convex is. Imagine that we just had to like revalidate this here, but also we would have to revalidate it inside of the widget, because remember, the widget is in question here too.
So that's how much time Convex actually saves that, right? You don't even think about it. So okay, I have to create a new account here. So Antonio, Antonio at example.com. Let's continue.
Let's start a new chat. Hey there. Let's send a message. And then in here, hey there, you can see our bot has responded and I'm going to mark this as resolved. And you can see it immediately updated for me, but it immediately updated for the user as well.
You can see I can't answer. This conversation has been resolved. And if the user goes inside of their inbox, you can see they have the checked sign as well. So that's the power of Convex. Two completely separate apps, but you can see that in this other app, it's completely real-time.
That's the power of a real-time database and the proper sync engine. That's why I wanted us to use this so much because it truly is worth it. Now let's go ahead and let's enable the tools to do this instead of us, right? Because this is great but what we want to see is the tool doing this. But let's just disable this enhance button if something is resolved because this shouldn't be enabled.
So instead of the conversation ID view, let's find the AI input button here. Let's disable it. If conversation question mark status is resolved. There we go. Great.
Now let's go ahead and let's implement the tools to allow AI to do this on our behalf. So in order to do this, we first have to create an internal method to resolve or escalate the conversation. So let's do that. Let's head back inside of packages, backend, convex, and let's go inside of system. In here, we already have conversations.
So we have a simple get by thread ID. And now let's do export const resolve internal mutation. Arguments are going to be thread ID, which can be a type of string. Handler is an asynchronous method. Let's go ahead and grab the context and arguments from here.
And let's do const conversation await database, my apologies, context.database, query conversations with index by thread ID. So we use the most optimized way to find this. Let's match the thread ID to arguments thread ID and only Let's grab the only one, the unique one. In case we couldn't find this conversation, let's throw a new convex error here. Let's just import it from convex values.
Code not found and message conversation not found. And message conversation not found. And now let's simply do await context database patch conversation underscore ID status resolved. As simple as that. And now let's go ahead and do the exact same thing but for escalation.
So let's copy, paste, let's rename this to escalate and let's change this to escalate. Escalated. Now that we have the internal functions set up let's go inside of our back end again but this time let's go inside of the AI folder and let's create a new folder called tools and inside of here let's go ahead and let's add resolve conversation tool so resolve conversation.ts let's go ahead and let's import create tool from convex dev agent. Let's import z from Zod. Let's import internal from generated API.
Let's import Support agent from agents support agent. Looks like Zod is missing here. So let me quickly check what is our version of Zod. It is this very specific version because we have that error. So now I'm just going to do pnpmf backend add Zod at, let me just fix this, this specific version.
So let me just add this to my back end here there we go. Now this should be working just fine great And let's export const resolve conversation create tool description here will be resolve a conversation. Arguments will be z.object and let's just pass in an empty string and then handler will have the context in here so we can grab some well context. Let's first check in case we don't have the thread id there is nothing we can do here except returning back missing thread ID. And now let's simply do await context and let's call run mutation because this create tool is the equivalent of action so we cannot mutate it directly instead we have to refer to our recently created internal methods and let's call resolve and pass in thread ID arguments thread ID its context thread ID It's context thread ID.
And let's now await support agent save message context thread ID, context thread ID message open an object role assistant and content of the message will be conversation resolved. And let's return conversation resolved. So what is the difference between our return and our well this method. So return is simply so the tool returns something right. We could technically return the ID of the updated conversation.
We could fetch the entire conversation and return that. It depends how they want to use this. In my case, I don't want the user to see the actual tool calling. I just want it to feel like magic. I just want this to happen and then the AI send the message, okay, it's resolved, that's it.
Depending on what kind of app you were building, perhaps it would be more useful for you to load the entire conversation and then show that to the user, right? It depends. You can and you absolutely should be looking to convex dev documentation here. Inside of their agents, here you have the tools. Let me just click on it.
There we go. And in here you can see exactly how all of them work and you can also see that you can actually make use of these arguments if you want to search for something in the database. Spoiler alert we will be doing that but you can see them being used here more in depth and also one super cool thing they have is the playground which is an Amazing way to debug the agent because they are annoyingly hard to debug If you have no idea what's happening with the tool if your tool stops working and you have no idea what's happening I'll show you how to set up playground so you can debug it easier. All right and now we have our resolve conversation tool. Let's go ahead and do the same thing but for escalate conversation.
So I'm going to copy this tool, paste it and I'm going to rename it to escalate conversation. Let's go ahead and call it escalate conversation escalate a conversation and same thing here except we're going to call escalate. And inside of here I'm going to say conversation escalated to a human operator. And let's return the same thing here. Great.
Now we have those two tools. And how do we add tools now? Well let's go ahead and look at the documentation here. So you can provide tools at different times. You can provide them in the agent constructor themselves.
You can do it when you call create thread, continue thread. You can call, we can add them when you add generate text or outside of the thread, right? So all of these options are supported. Specifying tools at each layer will overwrite defaults. That's what's important for you to know, but I think in the context of our app, it doesn't really matter.
So let's try and let's add them to our support agent here. So let's add tools here, and I will add resolve conversation from tools and let's add what's the other one? Escalate conversation from tools. And let me just see if I added this correctly or not. Oh.
I think this has to be an object. Yes that's correct. And now let's go ahead and add some more instructions here. Use resolve. OK let's let's open back this Let's open back the X here.
Use resolve conversation tool when user expresses finalization of the conversation. I don't know. Use escalate conversation tool when user expresses frustration or requests a human explicitly. And let's save this. Make sure that you're tracking your backend here and that everything is working fine.
You can see I had some errors but they got resolved. And now let's try this. So refresh your widget here. Let's go ahead and start a new chat. So let's track it in real time.
Here I am in the operator here I am in the user and I will say I am very frustrated. I want a human. Let's see what will happen. I seem to be getting some errors here. So let's see Can't read properties of undefined here.
So something seems to be going wrong All right, let's see we have something convex helpers Not sure what it is, but something it's definitely tried to call a tool, but then it failed. So I'm going to go ahead and debug. All right, so I managed to resolve it. And the way I resolve it is just by changing where I add my tools. So it looks like I told you incorrectly.
It's not exactly whatever when it comes to where you want to add these tools. So let's remove them from the support agent and I was actually a little bit worried about this because this seems like a circular reference. So let's actually remove both of these imports and just leave the support agent alone but leave the prompt as it is and then head inside of messages which ones inside of packages back and convex public messages so these are the ones that the user creates and find create action and in here when you call support agent generate text, after prompt add tools and now add the escalate conversation and resolve conversation. Both imported from their respective tools. Let's go ahead and wait for this to finish to ensure that everything is working just fine.
Let's see. There we go. Let's refresh again. You can see that I tried some of these here. So here I am again, my most recent chat here.
Let me just okay I have to refresh this just a second so I can select it. There we go. Okay so this is now selected. You can see that this is the first message. It is currently in unresolved status.
Let's go ahead now and send a message. I am very frustrated. I want to talk to a human. And let's send it. And let's see what will happen.
Conversation escalated to a human operator. Escalated to a human operator and you can see it was automatically escalated the new status here is now escalated exactly what we want to see and you can This is how this app is intended to be used. So your operators should only look at this escalated filter, right? Because the AI bots are responding to everything else. You should just be looking here at the escalated and then, you know, you would wait for someone to send a message.
You don't get any notification until a user says, I want to talk to a human and then it gets escalated. It appears here and then we go ahead and write, hey there, human here, you asked for me. That's how this is intended to be used, right? And now let's check if the Resolve method works. So let's go ahead and now bring this back into all.
So we are now tracking this one and let's say, sorry, you can close this accident. Conversation resolved, you can see the user can no longer write. All of this happened thanks to AI without us having to do anything. I think this is super cool. And you can see how unbelievably easy it was for us to do this.
It's so intuitive, all thanks to this convex agent component. Amazing job. That's exactly what we wanted to do. Now what I want to do is I want to go and stay inside of this messages here. So specifically inside of this backend convex public messages create.
So what happens now? Well, we shouldn't always trigger AI. We should trigger AI if the subscription is active. That's the first thing. But another thing is only if the status is unresolved because escalated means the user is talking to a human.
So it would be quite weird if in the middle of the conversation, suddenly AI responds back. Those are three people talking for no reason. So let's create constant should trigger agent. Conversation.status is equal to unresolved. Later we're going to add or subscription blah blah blah but we don't yet have it.
And then let's do if we should trigger the agent then call this entire function. Otherwise, we're going to do something else. So let's indent this. Let's do await support agent and let's simply do save message, context, thread ID and Prompt. And I think I have to both do from here.
Like this. And Let's see, is this exactly the same thing that we are actually doing? Basically we have to do the same thing that we do here in the public conversation. So yes, you can call save message directly from convex dev agent Or you can do support agent save message. So let's be consistent in our case.
Let's import save message. Will this actually work? I think the second here needs to be components.agent. Yes. And you can see it's the same thing.
And you need to import components from generated API. So let's try our app now. So I'm going to open a new one here and I will do, hi, how are you? Let's do that first. So right now we're talking with the AI And let's go ahead and say, what is your name?
And you can see AI keeps responding. But if I say, I want to talk to a human, you can see immediately conversation escalated to a human operator. And what's important now is that if I ask again, what is your name? The AI no longer responds. That's what we wanted.
Only the message is saved. Hello, AI is no longer responding. This is now only for humans. Hey there, human responding. There we go.
And no AI in between. Unless the operator marks this as resolved so now no one can answer. And then we bring it back to unresolved and then you send the message again hello there and now AI will respond again until this is escalated again that's how our flow works it will be quite rare that you go from escalated back to AI but it is the cycle that we allow to happen. Excellent, amazing, amazing job. So now let's focus on the operator dashboard.
Let's import the pagination here, the infinite scroll, and let's go ahead and implement this enhanced tool here. Let's go ahead and let's go back inside of conversation ID view and inside of here Let's go ahead and add the imports for infinite scroll. So use infinite scroll from workspace UI hooks use infinite scroll and infinite scroll trigger from workspace UI components infinite scroll trigger. Let's go ahead and first define the useInfinite scroll. I'm going to do that right after our useThread messages.
Let's go ahead and do this. Now let's go ahead in here and let's add all the properties. So status will be messages.status, messages.loadMore and loadSize will be 10. From here, let's get top element ref, handle loadMore, canLoadMore and isLoadingMore, All of those elements. And now what we have to do is we have to add the infinite scroll trigger.
We can do that right here inside of our AI conversation content. Remember these are chat messages so we go in the opposite direction to load all their messages. We go up. So infinite scroll trigger is a self-closing tag and inside of here let's simply add all of those props that we just destructured from our hook. Can load more, is loading more, handle load more and top element ref.
And now when you go to the top you will see no more items. As always, you can test this out quite easily by limiting the initial number of items and the load size to a small number like 2 and disable the observer. Refresh and let's try it out. You now have to manually load 2x2 messages until you get to the end. So, it works.
Now let's enable the observer and bring the initial number of items back to 10. Now let's create a simple tool that will help the operators enhance their prompts. So in order to do that I want to go inside of packages, backend, convex, and let's go inside of private messages.ts. And let's go ahead and let's import generateText from AI package. And now let's go ahead just after this, I mean above this create function and let's export enhance response.
And Let's call action from generated server. And in here we're going to accept a few arguments. Prompt which is a string and thread ID which is a string as well. And let me just check. Actually, yeah, I was thinking, okay, I'm doing the normal validation here, but actually I don't need to, right?
Because we pass the prompt manually. So actually all I need to do here is just confirm that the user is logged in. That's the only thing I need here. And eventually I'm going to check if this user has active, if this user's organization has premium so they don't waste my OpenAI tokens, right? So let's just go ahead and copy this part for now.
That's the only thing we care about. And we can also add organization because that's how we're going to check if they have subscription in the future. So let's go ahead and just confirm the user is logged in and they have organization. And then what we're going to do is just const response await generate text which we just imported model can be open AI from AI SDK open AI or whatever you ended up using. Maybe that was Google, whatever you have, right?
And then just go ahead and select a model. Preferably, you would use a cheaper model for this because this is like super small thing and then pass in messages here. And let's go ahead and first define role system content. And inside of here let's go ahead and write enhance the operator message to be more professional, clear and helpful while maintaining their intent and key information. Like this.
And then I will just add role user content prompt. So depending on, you know, arguments.prompt, Depending on how good you want this function to be, you could technically extend it by adding the context of all the previous messages that happened, right? For that, you would need to load the conversation, but I'm just trying to keep this as a simple, useful tool. So let's just return response.text here. That's it.
That's our enhanced response method. Now we can go back instead of conversation ID view here And let's go ahead and let's just add that. So I'm going to do that here. Const. EnhanceResponse.
Use action from convex react. Make sure you've added it here. API.private messages. EnhanceResponse. And let's do const handle.
EnhanceResponse. And okay so now we just have to let's do this inside of try-catch So console error error. And inside of here, let's do cons response await, enhance response, and pass in prompt to be our current value. Let me think of what is the best way of adding that here. Let's do this.
Const current value, form get values message. And then in here, passing the current value. So that's what we currently have typed. Will that work? Await, enhance, response, this needs to be async.
Like this. Great. And then once we get the response let's do form set value message response. As simple as that. And then let's also add const is enhancing set is enhancing use state false By default.
Make sure you have UseState. I think we already have it here. Go ahead and immediately turn it on here. Set is enhancing, set to true. And then open finally here.
Set is enhancing, set to false. Great. We now have HandleEnhancedResponse and now we can use it down here inside of our AI input button on click handle enhanced response so this will be disabled if it's resolved or if is enhancing and Then we can change this to if is enhancing. Let's say enhancing or enhance and let's also disable it if form form state is dirty my apologies is valid so basically we can't enhance an empty string So this needs to be valid so let's add exclamation point. So if it's not valid we disable this button.
Let's try it out. You can see by default it's disabled. Same as the submit button. And I'm going to say what do you mean and let's click enhance. There we go.
That's exactly what I want in this tool to do. Amazing. And let's also while we are enhancing, let's also disable the input so it can't be typed in. So we're just going to do... There we go.
We already added it to do here. We can now remove it. Is enhancing. And I think we have to do this somewhere up here maybe. Maybe not.
Is enhancing. Okay. So here. Is enhancing. Perfect.
And there we go. Now let me just... Also, you can add dev indicators false in your next config if you want to hide this. In this project, it's currently not helping us too much, but it is interfering. So instead of my web app, I will go inside of my next config here.
And let me just go inside of next config and add the dev indicators false. I am going to track that my web is being rebuilt here. Let me refresh and let's see if this will now disappear. And now that we have this solved, let's create a nice loading element for this. We have a nice skeleton here, right?
So why not add a nice skeleton here as well. So instead of the conversation ID view, let me go all the way to the bottom here and let's do export const conversation ID view loading. And let's go ahead and let's return a div with a class name. And we're now basically recreating the header. So flex height full, flex column and background color muted.
Then let's create the actual header. So header, class name, flex item center, justify between border, bottom, BG, background padding 2.5. And we're just adding this button with more horizontal icons. So the exact same thing that we are doing here, right? In the actual conversation ID view.
I'm just recreating that header component. Now, outside of that header, let's add AI conversation here. Let's go ahead and give it the maximum class name of maximum height calculate 100 pH minus 180 pixels. When you hover over it, it should look like this. If you have IntelliSense CSS Tailwind extension.
And then in here let's add AI conversation content and then in here let's go ahead and create imaginary messages so array from length 8 dot okay not like this length 8 underscore and then index and then in here let's check Every second message will be from the user. So let's use the modulus operator here. Then let's define some random widths. So we randomize the skeleton with 48, with 60, with 72 in an array. And let's now choose the random width from the number of items here.
So the width will be widths and then choose the by index and modulus of the width length array. And then in here let's go ahead and let's return a div. Let's go ahead and add class name here and let's use CN from workspace lib utils. Group flex full width, items and justify and gap to py2 open square brackets and target the div maximum width 80% and then if isUser let's go ahead and do isUser class, otherwise isAssistant, and flex-row-reverse. So The reason we are adding these is because they are targeted by these components.
They are not actually doing any CSS. They are just targeted within AI components. Let's give it key of index and inside let's render the skeleton component. Make sure you have imported the skeleton. Let's go ahead and let's give this a class name.
And let's do it like this. Height 9, pass in the width, rounded large, bg neutral 200 below that another skeleton and this one will have a class name size 8, rounded full, BG neutral 200 like this and then outside of AI conversation open up a div and let's recreate the input so padding 2 AI input inside of the AI input AI input text area, which is a self-closing tag. Make sure it's disabled. Placeholder will be type your response as an operator. AI input toolbar.
AI input tools. Let's just close it. AI input submit, disabled, status ready, and self-closing tag. That's it. And now let's go ahead and let's use this.
So I'm going to go inside of the conversation ID view here. And before we return, let's do the following. If conversation is undefined, that means it's still loading. Or if messages.status is loading first page, return. Is loading first page.
Return. And let's use our conversation ID view loading. Like this. And now when you refresh, you should have a nicer, you saw it for a second, it looks much nicer now. And this field is disabled.
I like it more this way, right? So now it matches our skeleton here. Great, amazing, amazing job. We added pagination here, we added loading skeleton, we added tools, we added this tool, we added the statuses here. Amazing.
I believe that's all we aimed for this chapter. We implemented manual status change, tool-based status change, enhanced prompt function, infinite scroll and loading to the dashboard chat. And just to clarify something, all of these prompts that we have inside of our AI whoops inside of our AI in the back end, so like support agent, All of these tools, all of these prompts, I will share you my prompts that I use ChatGPT to generate, which are much larger than this. But just for this example, we're using the simplest prompts so it doesn't accidentally break. These are super simple to understand for the AI and they seem to be working, but I do have a constant prompt and I will share that with you in the assets folder.
But for now let's merge this. So I'm gonna add all of my changes here and I'm gonna use 19 AI tool calling, AI tool calling, I'm going to open a new branch, 19 AI tool calling, and I'm going to publish the branch. And then I'm going to go ahead and review my pull request as usual, just in case we had some security issue or some serious bug here. And here we have the CodeRabbit summary. So let's walk through the walkthrough this time.
This update introduces conversation status management, messages enhancement, and UI improvements to the dashboard. New backend mutations and tools enable escalation and resolution of conversations, as well as AI-powered message enhancement. The frontend adds infinite scrolling, status toggling, tooltips and improved loading states with new button styles and a reusable hint component. Down here we have a sequence diagram pretty much explaining the total cycle that we went through basically when user clicks on one state it becomes another and then full circle. And in here we have some bugs.
Well in here it's telling us to start adding the toast messages. We're going to start doing that from the next chapter, I promise. And in here it recommends throwing errors. I'm not 100% sure of that because this is the create tool function And I think that the correct way is to return a string like this, which automatically means that what is suggesting here to wrap that inside of try catch and then use that error that's been thrown doesn't really work. So I'm going to leave it as it is simply because that's how I developed it initially so I don't want to do something that can break your app.
And in here we have a bug, not a big one but yes I completely forgot to pass side and align to my tooltip content. I never passed it. So we will do that in the next chapter. For now, we can merge this pull request. Amazing, amazing job.
Let's go ahead and go back inside of our main branch and let's click on the synchronize changes button and after our changes have synchronized as always let's double check inside of a graph perfect so we did 19 and then we merged it amazing I believe that marks the end of this chapter and see you in the next one.