تماشای این درس نیاز به اشتراک حرفه‌ای دارد.

Image Iteration Overview0:00

Alright, welcome back, everybody. So this next episode, I think, is going to be a lot of fun. We're going to move on to AI-generated images, and we will tackle it in such a way that allows us to iterate on an Idea. For example, it's almost Christmas at the time of this recording, so why don't we, say, create an image of Santa Claus, and then we will iterate on it and say, he's holding coal in his left hand. And then we'll say, he's pointing at the camera in his right hand. And then finally, we'll say, he's laughing maniacally, right? So we'll have ways, we will offer ways to iterate on an original Idea to tweak the image.

Initial Image Page Layout0:35

And then finally, we'll say, he's laughing maniacally, right? So we'll have ways, we will offer ways to iterate on an original idea to tweak the image. So when we're done, we'll have four different variations on a picture of Santa Claus. Okay, I think it's going to be a lot of fun. Let's get going. Now, if I visit my routes file, just to save us a little bit of time, I've done some initial view prep work. So notice the homepage loads this view called image. And within here, we effectively have two columns. The first column has a form with a text area with the name of description.

And within here, we effectively have two columns. The first column has a form with a textarea with the name of description. And this is where you describe the image. And then you can submit that form. The right side right here will eventually contain all images that were generated by OpenAI. Okay, so if we have a look in the browser, this is what we get. And yeah, again, nothing fancy at all. It's just a quick five minute layout. Okay, so let's think about it.

POST Route and Validation1:26

It's just a quick five minute layout. Okay, so let's think about it. When I submit this form, where should it go? Well, why don't we have it post to, how about just a simple image endpoint? All right, we have our next step. Into our routes file, I will create a new route that listens for a POST request to /image. And this is where we will send the request to OpenAI. Now, I said that textArea had a name of description. So why don't we go ahead and validate the request to ensure that we receive one.

Calling OpenAI Images API1:55

Now, I said that text area had a name of description. So why don't we go ahead and validate the request to ensure that we receive one. So description is required, needs to be a string, maybe a minimum of three characters, and I'll leave it at that. And that'll give us our attributes. Okay, so now check this out. It's actually alarmingly simple to generate these images. Let's go ahead and do it inline. So I will pull in the OpenAI facade. And then I can say OpenAI::images()->create().

So I will pull in the OpenAI facade. And then I can say OpenAI images create. And yeah, as you can imagine, I need to pass a number of parameters along with the request. So in phpStorm, I will command click on create. And one cool thing that the OpenAI PHP package does is it includes links to the corresponding documentation. So let's have a look. All right, here we go. So for the request body, the only requirement is a prompt. And of course, this is the description that you want to use.

So for the request body, the only requirement is a prompt. And of course, this is the description that you want to use. So let's do that now. prompt will be attribute description. And then if I switch back, we can optionally include the model. So we could do dolly2 or dolly3 at the time of this recording. The number of images that we want it to produce. Just keep in mind for dolly3 right now, that is limited to one. We can set a quality, SD, standard, or HD. responseFormat, the size we want, a style, and a user, which is mostly used for monitoring.

We can set a quality, SD, standard, or HD. Response format, the size we want, a style, and a user, which is mostly used for monitoring any abuse of the API. Okay, so let's leave it with prompt and model for now. And this will be dolly3. Cool. So that's going to return a response. And all I want to do right now is dd that response. And then we'll have a look. All right, let's do it.

And then we'll have a look. All right, let's do it. Back to Chrome. And let's stick with the placeholder text here. A beagle barking at a squirrel in a tree. And we'll give that just a second. All right, here's our response. Data will include all of the images it generated. And notice that the default response format is to include a URL to the corresponding image. So let's grab that here and open it.

And notice that the default response format is to include a URL to the corresponding image. So let's grab that here and open it. All right, and yeah, it works. I mean, come on. I know we're all instantly jaded at this point, but it is ridiculous how easy this is to do. It's really amazing. All right, so let's go ahead and grab that URL by going into data, the first item, and URL. So I can say data, give me the first one, give me the URL. And this will now be a URL to the image. Okay, let's do this.

Rendering Returned Image URL4:30

And this will now be a URL to the image. Okay, let's do this. Let's return a redirect back to the homepage. But I will include the image. All right, let's do URL. URL as a flash message. All right, so now I can fetch that out of the session in the view, or I can just pass it directly from our controller after we perform that redirect. So that would be something like this. session URL default to false.

So that would be something like this. session URL default to false. So I'll leave it like that. Okay, so now if I come back to image and I scroll down, I could say something like this. If we have a URL, then render it. Otherwise, display no visualizations yet. So we could do something like this. URL. All right, let's have a look in the browser.

URL. All right, let's have a look in the browser. Once again, I will paste that in. And there we go. It's working. Very, very cool. Okay, so now I want to iterate on it. And first, let's set a maximum width of like, I don't know, 250 pixels. Something like that. Okay, so now think about it.

Adding Iteration via History5:35

Something like that. Okay, so now think about it. If I want to tweak this and say the Beagle has a red shirt on or something like that, how would we do that? Because at the moment, well, I submit this form. It then takes us back to this endpoint where we start all over. So it's almost like we need a way to remember the history. Okay, let's get to work. So why don't we do this? Let's go back to our Chat class.

So why don't we do this? Let's go back to our Chat class. And maybe now, if we want to support creating images, maybe the name Chat is no longer appropriate. Why don't we consider renaming it to Assistant? All right, and notice that does rename the file itself. So now I could say something like this. Let's comment this out temporarily. So now I could say create a new Assistant, like so. And then let's have a look at our class API. I can ask it for speech.

And then let's have a look at our class API. I can ask it for speech. I can send it a message. I can reply to it. And I can also ask it to visualize an image based on a description. So that would be something like this. Okay, why don't we allow for that? Right down here, how about, yeah, right here. Visualize based on a description. Okay, so now we're just going to grab what we did here and migrate it over to our method.

Visualize based on a description. Okay, so now we're just going to grab what we did here and migrate it over to our method. All right, reformat. Update the prompt. And then return the URL as a string. Like so. Okay, we still have work to do, but let's see if we can get this to work. So this now gives us our URL. And then we redirect back to the homepage and we flash the URL. Yeah, you know what, I'm not even going to check this because I already know it works.

And then we redirect back to the homepage and we flash the URL. Yeah, you know what, I'm not even going to check this because I already know it works. Okay, so let's keep going. Next, it might be nice in certain situations to override the default options that we send to OpenAI's API. So, for example, maybe in certain situations I want to use the older model. Or maybe I want to request ten different images instead of one. Well, right now, we don't really have a way to allow for that. So, one thing we could do is merge our default options.

Well, right now, we don't really have a way to allow for that. So, one thing we could do is merge our default options with any that are passed in. Like this: array options. And then what I could say is, all right, well, let's update options to merge in our defaults, which will be exactly what we have below on this page. So the prompt and the model, it's all E3. And then we will merge that in with any potential options that you pass in when calling this method.

And then we will merge that in with any potential options that you pass in when calling this method. Finally, I can replace all of this with our options. Yeah, and that's one easy way to handle this. And yeah, of course, keep in mind with array_merge, any options that you pass will override the ones that we have set here. Okay, so yeah, maybe with that in mind, we could even add a third array where we hard code the prompt. So that we don't even allow the user to tweak that.

we could even add a third array where we hard code the prompt. So that we don't even allow the user to tweak that. But it's probably fine. All right, this looks good. Next, though, we still have this problem where every time we hit this post endpoint, we create a new assistant where we send a description through. So at what point do we allow for a historical record of the conversation, right? So that I can say, Santa Claus, he's holding coal.

of the conversation, right? So that I can say, Santa Claus, he's holding coal. He's pointing at the camera, right? Right now, every time we type into that text area, creates a new description, and we ignore everything that came before it. So with that in mind, what if we did this? Let's go into Assistant, and just like with chat, why don't we save a message? So right down here,

why don't we save a message? So right down here, we'll start by writing to the messages array, and the content will be the description in this case. And then OpenAI will generate one, and we will save the URL as well. Like this. So this will be the assistant's response, and the content will be the URL. Okay, but next I want you to notice how

and the content will be the URL. Okay, but next I want you to notice how all over this file, we have logic for adding a new message. We see it there, and there, and there, and there. And yeah, it's not the end of the world, but keep in mind, what if at some point we need to perform additional logic as part of adding a message? Well, right now, that additional logic would need to be pasted in every single location.

Well, right now, that additional logic would need to be pasted in every single location. So with that in mind, why don't we extract this to its own method, like this? And maybe just something simple, like addMessage. All right? So the message will need, will be, I'm sorry, a string for the message, and then a string for the role itself.

I'm sorry, a string for the message, and then a string for the role itself. And maybe the default role will be user. All right? I want it to return the instance so that we can chain on it, and then we can update the content. All right, so yeah, we're just taking that logic and moving it into its own method. So now we can update it, like so.

and moving it into its own method. So now we can update it, like so. I can say this, addMessage, and the role will be system. And I can delete that. Cleans it up a little, too. Let's do another one. addMessage. A User role is the default, so I can just keep it like this,

A user role is the default, so I can just keep it like this, down here. Here's another one. This will be the response given by the assistant. All right. Where else? One here. And this will be the description.

One here. And this will be the description. And then finally, one down here, which is the URL provided by the assistant. Yeah, just a small little refactor there. Okay, so now the benefit to this approach is now we do have a historical record of our conversation, even as it relates to

a historical record of our conversation, even as it relates to generating images. Okay, but the problem is even though we are tracking those messages, I'm still just sending the description to OpenAI. So it's ignoring all of the former messages, which I don't want to do.

So it's ignoring all of the former messages, which I don't want to do. So maybe I could do something like this. Description. Let's collect all of the messages into a Laravel collection. So remember, those messages will be what I said to OpenAI, and then how it responded, and then how I responded, and then how it responded.

and then how it responded, and then how I responded, and then how it responded. So the only ones I really care about are my messages. So where the role is user, pluck the... What did we set it to? content. pluck the content and then implode it

Content. Pluck the content and then implode it into a sentence, where a space separates each item within that array. So hopefully that makes sense, right? All of our descriptions are going to be added to the messages array where the role is set to user. That's the way this works. So what I'm doing is at that point, I would then have an array of

works. So what I'm doing is at that point, I would then have an array of all of my descriptions. So we pluck the content out, and then we implode that array back into a longer string. So if on the second iteration, I have SantaClaus, he's holding coal in his right hand, well, that's going to implode it into two sentences.

in his right hand, well, that's going to implode it into two sentences that we send to the image endpoint at OpenAI. So hopefully, I think that should work, and if we want to check it, why don't we go ahead and log the description to the console. Okay, so let's try it out.

to the console. Okay, so let's try it out. Back to our rots file. We instantiate our assistant, we visualize an image, but now I'm going to call it again so I can get rid of that, and let's tweak it, and I'll start by hard coding it. So if we, if this is SantaClaus, I will say he is holding

if this is Santa Claus, I will say he is holding coal in his left hand or something like that. Santa Claus. So we submit that, and while that's going, if I go into my log file, there's the prompt. All right, give that just a second, and there we go. He's

prompt. All right, give that just a second, and there we go. He's holding coal. So if I switch back to my log file, we get Santa Claus, oh, and I'm including the URL still. All right, we'll fix that, but then at the very end, it should say he is holding coal in his left hand. So yeah, notice we're just imploding both of those descriptions along with the next request.

both of those descriptions along with the next request. And actually, just keep in mind, this is one way to do it, and it's fine. There are also different endpoints that OpenAI offers to, for example, create a variation on an existing image or a number of variations on an existing image. Or you can edit an image where you're effectively

variations on an existing image. Or you can edit an image where you're effectively doing fopen on the image, php's fopen, and then you include that as part of the request. But we're not going to get into that in this video. Okay, cool. So let's figure out why it's trying to include the URL. Let's go back to Assistant, and let's see, right down here,

Let's go back to Assistant, and let's see, right down here, we call addMessage, that's right, and then down here, yep, that's right, we accept the message and the role, it's not being... Oh, I'm sorry. I know, you saw that, and you were just waiting for me to run into that error. Okay, but I don't even have to check our work.

were just waiting for me to run into that error. Okay, but I don't even have to check our work. I know that fixed the problem. So now, let's think about it. If I come back to route /web, well, every time we submit that form, we instantiate Assistant, and we start all over again. So it's almost like we need a way to remember

over again. So it's almost like we need a way to remember all of the messages from the conversation. And I think we can use, well, at least in our case, we can use sessions for this. So why don't we do this? Why don't we say session, and I'm going to put messages into the session, and I will do assistantMessages.

into the session, and I will do assistant messages. All right? So now, up here, let's update this to messages. And I can get rid of that. Okay, so now let's go into the view, and we're going to tweak this. We'll say, well, if we have any messages,

We'll say, well, if we have any messages, so we should always assume that it's an array, right? So let's set a default of an empty array if there is nothing in the session for messages. Okay. So if we have any messages, then let's loop over them. So for each messages

then let's loop over them. So for each message as message, let's do this. Let's do message content for each item. That way, we can take a look. Okay, so let's go through this, because I know it can be a little bit confusing. When we initially visit

because I know it can be a little bit confusing. When we initially visit the website, this messages array is empty. So we load the form, and then right here, we don't have any messages, so we just say no visualizations yet. We describe an image, and then we submit the form. That will then hit this endpoint

and then we submit the form. That will then hit this endpoint where we create an assistant, and then we pass that description to the visualize method, and then that will append to this messages array. However, when we do it the second time, well, we're doing the same thing. visualize will always just be a simple string.

well, we're doing the same thing. Visualize will always just be a simple string. So it's almost like we need a way to ask the assistant to catch up on the historical conversation, right? So something like this. I don't like catch up, but we'll start with this. I could say, well, here is the conversation

I don't like catch up, but we'll start with this. I could say, well, here is the conversation that I want you to catch up on. So I could say, go into this session and give me messages. It's almost like you're backfilling the conversation that the new instance won't know about. So if we had that, I could say

won't know about. So if we had that, I could say catch up on an array of messages. So you would call this initially when you instantiate the class. Like so. Or maybe we could even include this as part of instantiating the assistant. So when you make

as part of instantiating the assistant. So when you make a new instance of assistant, if you want, you can include the default messages. And you know what? I think I even like that a little bit more. So why don't we update this to construct and then move it to the top as you see here.

and then move it to the top as you see here. Yeah, I kind of like that. So now I can remove that and include it as part of the instantiation. Like so. Alright, that way we have a history of conversations that took place before we posted back to the server.

that took place before we posted back to the server. So if we did everything correctly, I think we should be in pretty good shape. So notice when we call this visualize method, we're still logging the description. So I'm going to clear this out, and we're going to start from scratch and see if it works. Santa Claus. Now keep in mind,

start from scratch and see if it works. Santa Claus. Now keep in mind, when it's done, we're not rendering the image anymore. We're just showing the text. So we'll probably see a very long string to represent the URL. But that's okay. We'll fix that in just a second. Alright, so here is my prompt, and here is the response. And if we have a look, that's

is my prompt, and here is the response. And if we have a look, that's what we sent to OpenAI. Alright, he is holding coal in his left hand. Submit. So now, yeah, Santa Claus is holding coal in his left hand is sent to OpenAI.

This segment does not contain any code-related terms that require formatting.

Switch back, and now we have that. Okay, so everything seems to be working. Let's just render it nicely. Let's go into our image view, and what I'm thinking is, let's wrap each one of these within a div. Maybe... Yeah, maybe wrap the loop in a div as well, and that way

maybe wrap the loop in a div as well, and that way we can add a little bit of spacing in between each one. And then, well, let's think about this, because messages is going to be an array that includes what I say along with the response. So I think one elegant way we can handle this is to chunk

So I think one elegant way we can handle this is to chunk the messages into sets of two. Because we always know the first item will be my prompt, the next item will be the URL. And then the item after that will be a new round, a new prompt, and then a new URL. So what I could do is chunk them.

and then a new URL. So what I could do is array_chunk the messages into sets of two, and that will be our chunk. And then, yeah, like I said, we always know that the first item in that chunk will be my prompt.

will be my prompt. And then the next item in that chunk will be the URL. Alright, and then once again, what did we have? Max width 250 or something like that? Yeah, I think that would do the trick. Let's do on the paragraph

that would do the trick. Let's do on the paragraph maybe font-bold, text-sm, maybe a little bit of mb-4. Alright, let's have a look in the browser. And yeah, real quick, before I switch over, keep in mind, because we stored the messages in the session, if we come back and refresh, we will see them.

stored the messages in the session, if we come back and refresh, we will see them. Yep, and there we go. So we had Santa Claus, and then we iterated. He's holding coal in his left hand. He's laughing like a crazy person. How about he is pointing at the camera with his right hand. Submit.

at the camera with his right hand. Submit. And yeah, once again, just to make sure we're clear, now we're sending a single prompt that includes all of the items that we referenced here. So let's see what we get in one second. Alright, at the very bottom. And this is amazing. Okay, very cool.

Alright, at the very bottom. And this is amazing. Okay, very cool. And yeah, really, the actual logic is incredibly simple. All the main, the core logic is what you see. Where are we? Here we go. The core logic is just this right here. Everything else is more almost like

is just this right here. Everything else is more almost like application specific to allow for collecting messages or remembering messages. But again, there are a number of ways that we could handle this, and different endpoints to generate a variation on an existing image or things like that. But yeah, I think this

on an existing image or things like that. But yeah, I think this looks pretty good. So let's get rid of the logger. Let's go back to routes/web.php. Let's simplify this. We then create a new assistant. If we need to, we can backfill it with historical messages or conversation.

If we need to, we can backfill it with historical messages or conversation. We can ask it to visualize an image, and then we can save that conversation for the next round so that we can keep going. And yeah, if you want on your own, add that button to reset the conversation. That button will be within a form. The form will make a

That button will be within a form. The form will make a POST request to reset or something like that. And reset will just say session()->flush(). That would be fine in this case. Or session()->forget('messages'). And then you form a redirect back to the homepage, and you're starting from scratch. Okay. So I hope you enjoyed that. If you want to tweak

from scratch. Okay. So I hope you enjoyed that. If you want to tweak it, leave a message in the comments and let us know what you came up with. Otherwise, I will, as always, see you in the next episode.

دوست دارید گاهی خبرهای Laracasts را ایمیل کنیم؟