Skip to main content
All CollectionsGetting Started
Learn to Build an AI app with Bubble
Learn to Build an AI app with Bubble

Video featuring Gregory John

Updated yesterday

In this course, featuring instructor Gregory John, you will learn how to create Social Rabbit, an AI application that transforms extensive blog posts into social media content tailored to specific platforms and tones. For example, you could adapt a detailed article about the Tesla Cybertruck into a concise, inspirational post for X.com.

You'll use Bubble’s API connector to integrate with OpenAI, with a particular focus on constructing effective prompts. This includes generating high-quality images that align with the blog content you are adapting. Additionally, the course covers the essentials of crafting a seamless user experience within Bubble, particularly when handling the inherent delays in AI processing with services like OpenAI.

Whether you are beginning your journey in AI development or looking to enhance your skills, this course is designed to equip you with the practical knowledge to build AI-driven applications efficiently.

🔗 Link to Bubble editor: https://bubble.io/page?type=page&name...

Transcript

In this course, we're going to be learning how to build an AI app with Bubble! We'll be using OpenAI to build Social Rabbit, which takes a long-form blog post as an input, and then formats it relevant for any social media post as an output. We'll also be able to adjust the tone of the post so that it's formal, humorous, or inspirational.

The key concepts in this course are: building the app in Bubble, connecting to OpenAI via the API connector, as well as the UX involved in creating a great app experience. Let's jump into the demo app and try it out.

So this is Social Rabbit. On the left-hand side, we have some inputs, and on the right-hand side, it looks like we are awaiting instructions, and this is where the results will be produced (which is the social media post). So if I click on this dropdown, I can select the social media platform. I'm going to select X.com. It has a 280 character limit. The tone I'm going to go with is "Informal". And in terms of the blog post I'm pasting in here, I found this Tesla Cybertruck review, and I've just gone ahead and copied this blog Post. I'm now going to paste it in. And if I scroll back up, you can see all of this long-form content that needs to be summarized for X.com with an informal tone. Let's try it out. I'm going to generate, and there you go! That took just a few seconds. And let's look at the results: "...Ever seen a vehicle that's more like an A-list celeb? Enter: the Tesla Cybertruck!" Fantastic. So what else can we do here? Let's try "Generate image." And there is our image relevant for our social media post. Okay, so there you have it. Let's jump straight into Bubble and start setting up our app.

So in order for this app to actually work, we need to use backend workflows, otherwise known as server-side workflows. These can only be enabled on a paid plan, but as I've created this new app, Bubble has offered me a free trial. So go ahead and accept a free trial because we're going to need it for the functionality for this app.

Okay, I'm going to skip the application assistant and here we go. I've just double-clicked on the page to bring up a Property Editor. And I'm going to change the page title to "Social Rabbit". Over on the Layout tab, I'm going to change the container layout to a column, and I'm going to change just the default builder width to 1200. This gives us a bit more canvas space to build on. Now, there are just a few styles I'd like to adjust slightly. This is optional. You can skip forward to the next chapter if you'd like. Otherwise, you can just follow along with for a few minutes while I adjust some of my styles, which means that what you're building in your end will look exactly what I'm building on my end.

So I am going to head over to the Styles tab. I'm going to click on "Style variables." First thing I'm going to do is just change the default font to "Inter." After that, I'm going to change my primary color. This is just going to be a dark primary color. I'm going to choose ones across, that's six ones. On the text, I'm going to choose twos across. On the surface, I'm going to choose #F7F7F7. Then, I'm going to set up two new color variables. The first one will be called "Dark grey," and here (in the Description) I'm going to say "Body text." Then, the second one will be called "Light grey," and I'm going to say "Borders."

Now I'm going to go over to my "Element styles", and I'm going to go look for my button. Here it is here on the left-hand side, I'm going to go to my Primary Button and we can see that the color has changed here, okay? Look in the Appearance tab, we can see primary color, ones across. That's because we updated our global styles for this particular color. I'm just going to change the font size to 16 for this button, and that's fine. I'm going to close the buttons on the left. Let's see.

I'm going to go down to Inputs. I'm going to open up "Standard Input". I'm going to change the boldness or weight to 500. Actually, one thing we do need to do is back in the "Style variables," just set these colors down here. Okay, so dark grey will be fours across. And light grey is going to be #EBEBEB.

Back on the Standard Input, I'm going to set the placeholder color to be dark grey, our new variable. Border style will be solid with a roundness of 8. Then the color is going to be our light grey, and let's have a look at the conditionals quickly. We're going to remove the hovered conditional, but leave the rest.

Let's do the same now for the Multiline Input. First on the Appearance, going to change it to medium 500. Placeholder color will be dark grey. Border style is solid with a roundness of 8, and then my border color will be my light grey. Lastly, on the Conditional tab, I'm going to remove the hovered.

Okay, almost there. Let's just have a quick look at our text. So we've got Body as the default, 14 pixels. I'm just going to bump up to medium and I'm going to change the font color to my dark grey. And then I'm going to do the same with the Body Large. This is 16, medium, and dark grey.

Okay, just having a quick look at the headings. We're going to be using Heading 3. I'm happy with the settings there. We'll use Heading 6 as well. So for Heading 6, settings are currently fine, just change the weight to semi-bold, and that's fine. Okay, I think that's what I wanted to do with the styles.

Let's now go ahead and set up our database and our Option Sets. So I'm going to click on Data, and then I'm going to go across to Option sets first. We can skip the tutorial, and in the demo I showed you we had a number of option sets there. We had two dropdowns: the one dropdown lets us select the social platform, and the other dropdown let us select the tone. There is a third option set called Status, where we can monitor the status of the post that has been generated.

So the first option set I'd like you to add is called Social Platform. Or actually, we're just going to use the word "Platform" here. Go ahead and create that. I'm going to type in three options here. The first being Facebook. Create. Next one, LinkedIn. And the last one is X.com.

Now with AI, we are able to feed through quite a lot of parameters, give AI instructions over how to format the text, the tone of the text, and in this example, we're just referencing the social media platform because of the character length, the max character length. So we're going to create a new attribute here which will enable us to assign a max character length, and we will refer to that max character length when we're building our prompt later on in the Workflow section.

So let's create a new attribute here, please. We're going to call this "Length," and this is a number. Now we can assign a number to each option set. Let's go ahead and modify attributes for Facebook. And the length is 63,206 characters. Go ahead and save that. Let's do LinkedIn next. Max length is 3,000 per Post. And lastly, let's modify attribute for X.com. Set that to 280. And yes, maybe by the time you watch this course, some of the limits have been lifted or slightly different. It doesn't matter. The point being is that we have control. We're giving instructions to OpenAI for the actual character length that we'd like returned.

The next option set we're going to create is going to be called "Tone". Let's go ahead and create that. And let's set up three different options here. Let's say "Formal," "Humorous," and "Inspirational".

And the last one we need to create is called "Status". And we're going to have two states here: one is "Pending," and the other one is "Complete". And how these two statuses work is when we initially click the button Generate, we are creating a Post in the database, setting the status to Pending. With that status, we can then create the UX experience I was talking about in the earlier part of this course, which is building a great user experience. So whenever users have to wait, well, you need to show them something, give them instructions, let them know that something is happening in the background. That's what the pending status is for. And then once it's been complete, we've got data back from OpenAI, and we've saved it to the database, we'll also update the status to "Complete" to then on the front-end on the user interface, we can then show the results. So pretty imperative that we have these two statuses so we can track the flow and the completion.

Let's head over to the "Data types" now, and we're going to create a new type called a "Post." Now, the first thing we're going to do is add a field for each option set that we've just created. First one is "Platform," and that is under Option Sets --> Platform. Next one was "Tone", and that is Option Sets --> Tone. And the last one is Status, and let's find our "Status" under Option Sets.

And then if we think back to the design on the left-hand panel, we had the two dropdowns - platform, tone - and then beneath that was the multiline input. So that would be called the "Source," and that is just text. So we need a "Source" field type of type text. And that is basic type text. Okay, this could also be called Input. Same thing.

Now we need "Output," and this is what we're receiving back from OpenAI, and we're receiving text back from them. And then something else I want to do is have "Output edited" because we are going to be outputting what we get back from OpenAI into a multiline input so that it's easy for a user to make quick edits if they'd like. And I think this is the best way to work with AI. AI is an assistant, but it's always nice to make sure that you can edit some of the work that it returns. We're going to choose text for that.

And then lastly, we notice that we're able to generate an image. And something to note is that OpenAI temporarily store the image file, but that eventually expires, so we actually need to save that image to Bubble - we can't just extract the link that has returned back to us, we need to save it to Bubble. I'll show you how to do that. So we need to capture and save that image to the database with a field name "Image" that is of type image.

Okay, now that we have our database set up, we can go ahead and do some design work. So I'm going to split this into two parts. The first part will be the left-hand panel with the inputs and the source, and then we'll do the right-hand panel, which is the output. And the right-hand panel actually has three separate views. We have basically the new post view, so nothing has been done. We're just in idle mode. The second is the Pending state: we have hit the Generate button and we're waiting for something to be returned. And then the third one is we now have a result. So we've got three different groups, all collapsed by default, and we're using conditional statements to show the correct one at the right time.

Let's head over to the Design tab, and if you If you click on the index in the element tree, we can bring up the Property Editor. And the first thing we're going to do is just change the background color here: type in #F7F7F7. Because I want our groups to stand out on this background.

Okay, now go ahead and grab a Group, please, and just drop it on the page. Don't worry about the shape if it's slightly different to mine. But let's do name this to "Group Container." And on the Layout tab, what we're going to do is choose horizontal alignment in the center and we're going to set a max width here of 1200.

Then scroll down and uncheck "Fit height to content" so that it just covers the entire page, and then let's just add some padding. So top, bottom of 96 and 96 and then left, right of 32 and 32. And the last bit of spacing we're going to add to this Group is on gap spacing at the top of the Layout tab, we're going to choose 32 Row gap (px). This will become evident once we start putting groups on the page, why we're doing this.

Okay, fantastic. So now we need to grab some text. Here it is here, and just drop it inside this Group Container. Now, the Group Container should be covering the entire page and what the purpose of putting down a Group Container is so that we can limit the width to 1200, so the page can stretch as wide as it needs to. And I'm on quite a big desktop, which you can see in view here and if there was no max width, then we would have this massive desktop that wouldn't be very usable. So we wanted it centered with a max width of 1200, that's why we're using Group Container.

I'm going to click just to drop the text in there, and let's type in Social Rabbit. For the style, we're going to choose "Heading 3". Now for that little icon, let's do this. It'll be fun. Let's go over to Plugins and let's install - actually, while we're here, we can install the API Connector, and this enables us to communicate with OpenAI - and then let's look for Material Icon. It's third in the row here, "Google Material Icons." Those are the only two plugins we need to use.

Okay, back to the Design tab, let's scroll down under Visual elements and find Material Icon. Click it once and then click it on the page. And I'm looking for a little rabbit. I think it's under the term Cruelty Free. So C-R-U, and that brings up a little rabbit. Okay, let's make the icon, let's make it the primary color. And on the Layout tab, I just want you to change the fixed width and fixed height to 24 by 24. It'll scale down nicely. Let's put this in a little Group as well. So I'm going to right-click and say "Group Elements in" --> "Align-to-Parent" container. Let's rename this Group "Group Icon." And I'm going to remove the min height and the min width. And let's add some padding all the way around of 8 pixels: 8, 8, 8, 8. Just having a bit of fun here, guys, we're going to get to the logic really soon. And then we're going to add a little border on this as well. So solid with our light grey - always going to use this light grey on borders - and then 8 on the roundness. And let's just make sure we have a background color of Flat color and then let's always just set a style here, so the primary contrast, which means now we have this neat little rabbit in this icon.

Moving on, we're going to highlight Group Icon, highlight Text Social Rabbit, right-click, "Group elements in" --> "Row container." Fantastic. We can call this "Group Header." And on the Layout tab, we need some separation between those two elements, so we're going to add column gap of 12 and then we're going to remove the min height as well so we just close down the height, got some space we're not utilizing underneath, and lastly, we can highlight Social Rabbit and make "Next." And there we go. Just a touch of design work. We're having fun here.

Okay, now I just want some subtitle text here, so I'm going to grab Text again, drop it in. I'm just going to give an instruction here: "Create social media posts with images." Okay.

Let's now work on our left-side Input group. So I'm going to scroll down to Containers, grab a Group, and just again just drop it inside your Group Container. Let's call this "Group Source." I'm going to detach the style because I do want to set up style here. So let's change the background color to flat color. Let's set up our usual border color with our light grey and with 8 on the Roundness - always going to be the same. And for now, let's just set a max width of, let's try 576. (This is just temporary, but I just want to create a basic shape to work within.) And let's do the same on the height, 576. We'll come back and adjust the exact sizing later.

So you can see that when I did this, we've got some overflow problems down here. So what I'm going to do is click back on Group Container, and I'm going to select "Fit height to content," and that will then increase the size of the page and this Group will fit height to the content within, which is this Group Source. Okay, back to the Group Source.

Now we're going to add some padding of 32 pixels all the way around - make sure it's padding the very last row and not margin, margin will give us a different result, we want internal spacing. Okay, we're ready to add some content inside.

Going to grab some text, drop it inside the Group Source. This will say: "What would you like to create?" Let's use a Heading 6 for this, and on the Layout tab, I'm just going to remove the "Fit width to content" just so it spans edge to edge. This is just for mobile or responsive reasons. And let's give another instruction beneath. So I'm going to highlight this text and I'm going to duplicate. And on my Mac, the shortcut is Command+D. It duplicates the text, and in here, I'm going to change it to Body Large, first of all, and then I'm just going to type, "Select the platform and tone then paste in the content you'd like to summarize." Yeah, just a bit of an instruction there.

So we can see that we need some spacing in this particular Group, so for that, we're going to use our gap spacing. We're going to just use 24 here. Okay. Now we need a dropdown. I'm going to choose my Input forms, look for a dropdown, drop that in. And I've just realized that this is the one thing I didn't style, so we're going to do that now, edit the style of the dropdown just so it conforms with the rest of my style choices. We're going to choose 500 medium. Placeholder color will be dark grey, solid border, 8 on the Roundness. You know what to do here, guys, light grey. And on the Layout tab, I'm going to match it with the inputs, and the inputs have 16 pixels of left, right padding so we've set the same for the dropdown, and then we don't have hover on the Conditional tab. I'm going to delete that. Okay.

So Dropdown A. We're going to just select this dropdown that says "Dynamic choices" because we want to feed through our option set options here. So where it says "Types of choices," we can now choose "Platform." Then Bubble is saying, well, from that, which choices would you like? But I would like all of the platform choices, so I'm saying "All Platform." And then Bubble is saying, what text should we display in the dropdown? And that is going to be the Display. The length we're going to be using in our prompting later on, and it will have no default value.

Let's give an instruction for the Placeholder. Let's say "Select Platform." And on the Layout tab, let's just sort out the width here. We're going to remove the fix width and the min width and let's just say for this course and this project that the height is going to be 40 for inputs and dropdowns. Okay, good stuff. Let's duplicate this.

Command+D on my Mac, and now we need to allow the user to select the tone. So I'm writing, "Select the tone," and I'm going to change the platform to "Tone." I'm going to say, please show me all of the tone options available, and that will be the "Current option's Display," which is the written options that we set up in the option set.

Now for the actual blog post that we're going to summarize, let's grab a Multiline Input for this purpose, drop it in, and let's say "Paste your blog post here." Let's edit the style of the Multiline Input just to make it consistent with the other inputs, so 16 and 16. And let's actually do 16 all the way around, and I'm happy with the rest. Okay.

So on the Layout tab of this Multiline Input, let's remove the the fixed width and the min width. Then let's choose a fixed height here of 192. And the reason why we're creating a fixed height is because we don't want the input to grow with all of the text, then we can't find the button which is just beneath the multiline input, we'll do that next. So just good user experience here: smaller input, but enough space in there to be able to paste and edit and that kind of thing.

Okay, now let's grab our button. So I'm going to scroll up, find a button, drop that in. This will say "Generate post." And on the Layout tab, I'm going to remove the fixed width, remove the min width, I'm going to select "Fit width to content," and I'm going to say 40 so it matches our dropdowns. Now I'm going to position this on the right-hand side. Very nice. Okay, this is looking really good!

So we can see a bit of space down here, so I did a pretty good estimation when I estimated that on Group Source, the fit height to content, the min height would be 576, so we can just actually press backspace and remove that, but keep "Fit height to content" checked.

Okay, the last thing I want to do, just because I'm obsessed with design, is highlight this text up here and the text beneath, and put this in a new Group because I want the spacing to be closer together. Right-click, "Group elements in" --> a "Column container." I'm going to rename this to "Group Title & subtitle," and spacing here will be 12. We're doing this for the proximity effect, okay? We want text to be closer together, and then we want the inputs to be closer together, but further away from the text. Then we also want the button to be slightly separate, okay? This is just much easier or a better user experience by doing this.

Let's do the same for these: we're going to select all three of these inputs, right-click, "Group elements in" --> a "Column container," we're going to rename it to "Group Inputs" and then we're going to apply a gap spacing of 12. Okay. So the text is closer together, these inputs are closer together, and then the button is down here, 24 pixels away from that Group Input.

So that's the Group Source, and now we're going to deal with the Group Output on the right-hand side, and that has three different conditional states, the first being the idle state. This state exists when the page is first loaded, and a user has not clicked the "Generate post" button yet. We're including a little notice that we our waiting instruction. The second state is the pending state. We're showing a loader and letting the user know that their generation is in progress and that they need to wait to see the results. And lastly, we're showing the completed state. And this is the generated post itself. So let's jump into the design now.

Let's grab a Group in the container section and drop it above the left-hand panel Group Source. Let's name this "Group Output." Let me change from a standard group, what we're going to do is actually copy this style across. Okay, so let's say, create a new style. Let's call this "Card medium" and create. Now we can apply that style to Group Output. Perfect. On the Layout tab, just add a bit more height, maybe 160, and with Group Output highlighted, I want you to also highlight Group Source. Right-click, "Group elements in" --> a "Row container" because they need to be on the same axis next to each other.

Let's firstly rename this Group to "Group Content", and then we're going to apply some gap spacing of 32 pixels on the column. Looking good. So this Group Source needs to be on the left, so from the Layout tab, we're going to choose "Make first." Fantastic. Now we can crack on.

So we've done some great design work here, Group Title & subtitle, and I actually want to copy that across and place it inside Group Output. Now, before we do that, I want to change the container layout to "Align to parent," and then we're going to choose "vertical stretch" so that this Group stretches as much as the left Group stretches.

Okay, now for our three different states. So this state would be for the actual result, but why don't we start from the beginning, so the Idle state. So let's grab a Group, from the containers and we can drop it in the center. I'm going to call this "Group Idle," and then I'm going to grab this little rabbit icon, command+C on my Mac, and then paste it in. I'm going to choose horizontal alignment in the center. I'm going to grab this text here, Command+C on my Mac to copy and then Command+V to paste and I'm just going to change the text to "Awaiting instruction."

On the Appearance tab, I'm going to choose center, and then I'm going to duplicate this text using command+D, I'm going to select "Body" for the Style and put that in the center. And then I'm going to type "We are ready to produce your post." Okay, and it looks like it just needs a touch of gap spacing on the Layout tab - let's just choose 8. Very nice. All right, let's bring this closer. Now, it's not going to be visible on page load, so please uncheck that on the Layout tab of Group Idle, then we do want to collapse it, and we're going to show it based on the condition. And that condition we'll work on a bit later on.

For now, we can go over to the Elements Tree and we can use this eye icon to hide it. Now for the Pending state, we're going to do something similar now. Grab a Group, drop it in the center: "Group Pending." It's not visible on page load, and we will collapse it when hidden. Let's grab an Image element now. Drop it inside. And I'm going to upload my loader. You can basically copy this from the demo app, and you can find the link above. On the Layout tab I'm going to choose 56 with a fixed width, and I'm going to keep element aspect ratio fixed as a one to one, and then finally put that in the center. Going to change the naming to "Image Loading."

Now I'm going to grab some text, drop it inside Group Pending, let's say "Generation in progress..." Let me center-align that, and then on the Layout tab, remove "Fit with to content." Looking good. Make sure it's collapsed and hidden and not visible on page load. I'm going to head over to the Elements Tree, close that down, and then hide it.

Now we need another Group. This time we're going to position the Group at the top, okay, up here. This will be called "Group Results," and that will not be visible on page load and it will be collapsed when hidden. And I want to increase the height here to 260. This is just a bit of guesswork - we need some space to work within, we'll deal with the actual height later.

Now, I'd like you to copy this text across, please. I just want to save some time on the design work. Command+C on my Mac, and command+V. Going to say "Here is your post." And for this piece of text, what we're going to do is combine static with dynamic text. And for the Placeholder, let's just get some text in here. I'm going to say "Here is your X.com post" - I could say Facebook post, LinkedIn post - "with an inspirational tone," and you know that the word "inspirational" will be substituted with dynamic data for whatever the person chose from the dropdown on the left-hand side. And let's say "Generate an image with the button below." Okay, fantastic.

So on the left-hand side, this "MultilineInput Source," I want to copy that across and then rename it, because when the post is generated by OpenAI, the user might want to edit it slightly, or add more to it or subtract from it. So we're going to push the results from the database into a multiline input so that they can quickly edit it and then save it to that "Output edited" field in the database.

I'm going to copy, go across to Group Results now, and paste. Make sure to rename this immediately, please: "MultilineInput Output." And now we need a button, so I'm going to take this "Generate post," copy, and I'm going to paste it inside. And by the way, another way to do this is, in fact, if we just duplicate it, command+D on my mac, over in the Elements Tree, we can actually move it into position! So here's the button here and I want it down here somewhere, I can just drag it down to position. Slightly out. There we go. This will say "Save post," and this will basically save any edits that have been made here. Okay, just checking on the row gap, it's 24. Let's add 24 pixels of row gap on the Layout tab of Group Results. Looking good.

Now we need a big button so that the user can generate an image! So let's, this is going to be a unique one-off style button, but let's go ahead and grab a button and let's drop it inside. Let's say "Generate image." And let's see what we have here, so we have a "Flat Button," yeah, so select "Flat Button," change the size to 16, let's add a border style of "Dashed," a roundness of 8, a width of 2 because it's dashed, and then our border color which is light grey. And then on the Layout tab, let's remove the fixed width, remove the min width, and I want this to be quite big. Let's try 48 for the min height. That looks really good.

And now we also need an image, so I'm going to click on Image, I'm going to drop it beneath "Generate image" and the multiline input. Let's name this to "Image Post." On the Layout tab, we're going to remove the fixed width and the min width. I'm going to set an aspect ratio of 3-2, common photography size. Then lastly, on the Appearance tab, I'm going to set a roundness of 8.

Okay, so obviously that image is not visible on page load, so on the Layout tab of Image Post, I'm going to uncheck "This element is visible on page load", and I'm going to check "Collapse when hidden." And on this Button Generate image, I'm also going to say not visible on page load and also collapse when hidden. So when there is no image, the "Button Generate image" will be visible, and when there is an image, then the "Button Generate image" will not be visible. (Just making sure that this is collapsed and hidden, yes, it is, okay.)

So now we're going to deal with the initial workflows, just the Bubble side things, so that would be creating a Post and setting a parameter to control which Group is shown at which stage. So let's dive in and have a look at these.

Let's set up the initial workflow for generating a Post. So let's add a workflow to the Generate post button and what we're simply going to do is press "Click here to add an action" and go down to "Data (Things)" --> "Create a new thing...". We know that Type is going to be a Post, and let's first of all click "Add all fields" with this little option down here.

Let's go through them one by one. Now, Image is going to be generated later on via the workflow on the button that we set up a few minutes ago, so we don't need that here (and we can delete this field here for now). The Output is what is returned from OpenAI, so we don't need that either. "Output edited" is what's saved from the Save button in the right-hand panel Group Output, so we don't need it, but these are the four fields we need to fill out: Platform, Source, Status, and Tone.

So we know the Platform comes from a dropdown, so you can enter "Dropdown Select Platform's value." Perfect. The Source comes from the MultilineInput. Now make sure you use the Source here, okay, otherwise you won't be sending any data to OpenAI (set to "MultilineInput Source's value"). Next is the Status. Now we're going to set this to "Pending." The button has been clicked, and now we're going to show them that the generation is in progress straight away. Then the Tone is coming from the dropdown, so you can enter "Dropdown Select the tone's value." And then naturally after this, we would be sending this data to OpenAI, but we first need to set up those API connections very shortly.

For now, let's just sort out the end of what this workflow is going to be. We're going to press "Click to add another action," go to "Navigation" --> "Go to page...", now we're not changing pages, we're just going to select the same page which is the index, because I want to access this section down here that says "Send more parameters to the page." I'm going to check that. This allows me to set a parameter. So I've clicked "Add another parameter." I'm going to call the Key "post" and what I'm going to do is grab the "Result of step 1 (Create a new Post...)'s unique id" for that value. So the Post that's being created, that bit of work that's been done, is the result and its ID. And because we are setting a parameter with the unique ID, we can then change the behavior on the page. So we're going to be referencing that parameter, that unique ID, for the rest of this course so we can show and hide relevant groups at the right time.

Okay, let's go back to the design, because now we need to look at when to show and hide these groups based on this parameter. So over in the Elements Tree on the left-hand side, let's open up "Group Idle." On the Layout tab, we can see that it is not visible on page load and it's collapsed when hidden. Let's add a conditional of when we can show this. We need to click "Define another condition" on the Conditional tab and we need to type the word "get" because I want to access "Get data from page URL" - I want to access the parameter we've just set in step 2 of the Generate Post workflow. The "Parameter name" is going to be called "post" of type Post. Easy. We're going to say that when it is empty, when get post from page URL is empty, that's when we show Group Idle. I just realized that I'm doing this to Group Results instead of Group Idle, so I'm just going to copy this and delete that and I'm going to open up Group Idle. Okay. So Group Idle is showing, I just didn't click on it before.

I'm going to paste this back in, and that's when it's visible: "When Get post from page URL is empty," that's when the element is visible, and it's not visible on page load. Let's copy this expression.

Now, let's hide Group Idle in the Elements Tree and open up Group Pending, and then I'm just going to make sure that Group Pending is clicked, okay, I'm on the Conditional tab of Group Pending, and I'm going to paste in this expression that I've just copied. And now I'm going to access the Status field, so I'm going to click on "is empty" to access "'s Status" and then select "is" and select "Pending." That's when it's visible.

And now for Group Results, let's select Group Results, Conditional tab, paste the expression in, we're going to access "'s Status," the operator "is", and the option "Complete." That's when that's visible.

Okay, so we have "when the parameter Post is empty, show the Idle state", "when the parameter post is pending, show the pending state", and "when it's complete, show this on the screen right now, Group Results." Easy.

Now, in order to be able to show the actual results in the multiline input with the image, we need a data source in Group Results. Okay, so I've clicked on Group Results, here is my Property Editor, I'm going to choose "Type of content" is a "Post," and the data source is basically the same pattern: "Get data from page URL", the Parameter name we called it "post" and that is of type Post. Close. Now we have a data source that we can reference.

So let's start down in MultilineInput Output. The "Initial content" will now be "Parent group's Post's Output," or what's returned from OpenAI. We'll deal with the Save button shortly. Let's deal with this image, Image Post. In the "Dynamic image" field, it's going to be "Parent group's Post's Image." Let's change the "Run-mode rendering" to "Zoom" for this. I want to zoom the image and cut the shape.

And now on the Save button (Button Save post), we're going to add a workflow. This will be very, very simple, this one. Step 1 will be "Make changes to thing...", where the "Thing to change" will be "Parent group's Post." And what we want to do is save the MultilineInput Output's value to the "Output edited" field. At that stage, we probably just want to show a little alert as well. Where is it? Here it is here. I'm going to drop it an Alert element in the Design tab and I'm going to write "Successfully saved." And maybe this alert can have a background color of our green, and I don't know, 10% opacity. Then on the Layout tab, let's put that in the center. That looks fine.

Let's go back to this "Button Save post is clicked" workflow, so that on the Save button, we can just add in that alert. At the second step here, we can just choose the action "Show Message," and Bubble has recognized that we only have one alert, and there it is there. Okay, so we're saving the post to Output edited, the MultilineInput Output's value, and then showing the alert. So that is the design section done with some basic workflows.

Now we're going to turn our attention to integrating OpenAI with our app. Let's jump in. Please navigate to platform.openai.com, and if you already have a ChatGPT account or a developer account, you can go ahead and log in because you can log in with your global OpenAI account details. If you don't have an OpenAI account, then please go ahead and sign up. I'm going to log in and I'll see you in the dashboard.

Before we continue, let's quickly review what happens when a user generates a Post. After clicking on the "Generate button," we're going to be sending OpenAI some prompt information based on the user's inputs. OpenAI will then follow the instructions and return some data to Bubble. Once that data, or in this instance, our summarized social post, is returned, we can save it back to the Bubble database and change the Status of the Post from Pending to Complete. And this enables the user to then see the generated post in the dashboard.

So now that you are logged in or signed up in the dashboard, there are two things we need to do: the first is we need a billing account. So the API through OpenAI is not free. You absolutely have to pay for it, and it's very cheap. So I have possibly $8 or $9 on my account, you could possibly top up with $1 or $2 for this course, and that would be enough. Let's just first have a look at how it's done.

I'm going to head over to this left-hand panel and I'm going to go to Settings. Once I'm in Settings, I'm going to go ahead to Billing, and you can see that I have a credit account of $9.35 and "Auto recharge is off," and I can add to my credit balance. Please go ahead and add a payment method if you don't have one, and then top up your account just with a few dollars.

Once you've done that, we need the API key. So we head across this left-hand menu, head into the API keys, and I've already gone ahead and created an API key for Social Rabbit, here it is here, and I've just saved it to my notepad. So once you've created your new API key or secret key, just save that to a notepad for now because we're going to need it soon when we are setting up the API call.

So I've saved my key to a notepad, and I'm going to head over to the documentation, and I'd like you to do the same. So OpenAI has just released GPT 4o. That's the model we're going to be using. 'O' stands for Omni, and it's a multimodal model. The two endpoints we're going to be using, the first is text generation, and that's going to take the blog post and convert it into our social media Post. We're going to be using the Chat Completions endpoint. The second one we're going to be using is Image generation and OpenAI has a model called DALL-E 3 for that.

Let's first have a look at the Text generation --> Chat Completions. So let's just read this first from the OpenAI documentation: "Chat Completions API", "Chat models take a list of messages as input and return a model-generated message as output. Although the chat format is designed to make multi-turn conversations easy, it's just as useful for single-turn tasks without any conversation." And that's exactly what we're doing: we're not starting a conversation, we are just sending some data with some context of what we want, and then we will be returned with the summarized social media Post.

Then it says "An example Chat Completions API call looks like the following". So we have our URL, which we're going to need. We have the Content-Type, which we're going to need. We have "Authorization: Bearer" and that's where we insert our API key. Then we have this body over here and we're going to need some of this, but not all of it.

If yours looks a little bit different, just make sure that you are on the cURL option. You should be by default. To save ourselves a little bit of time, what we're going to do is just click on this Copy icon, and then we're going to go ahead and set this call up by pasting this into the API Connector.

Let's head over into Bubble. I'd like you to navigate to the Plugins tab and the API Connector. If you didn't install the API Connector, go ahead and do that now by clicking "Add plugins" and then once it's installed, click "Add another API."

What we're going to do is scroll down to find this option that says "Import another call from cURL." We just clicked on the Copy icon, and that's what we're going to do. I'm going to click on that, paste it in, and then click on "Import." Okay, fantastic.

Now we need to move some of this data further up to this shared area which is more secure as well. So the first thing we're going to do is at the top, we're going to type "OpenAI" next to "API Name." The authentication method, which is in the documentation, is "Private key in header." We're going to move authorization up here to the shared area, so the value, I'm just going to copy this command+C in my Mac, command+V, then I'm going to delete this part here ("$OPENAI_API_KEY").

Now, go to your notepad and copy that secret key that you generated earlier. If you didn't save it, you'll need to go ahead and create another one. I'm going to paste mine in, and you will have access to this app but by the time you have access to this app, you won't be able to use the secret key because it will be deleted. So you won't be able to use my account to run these calls and test it out.

Now we need a shared header, so I'm going to click on the button that says "Add a shared header," and we're going to copy this content here. So the key is "Content-Type" and the value is "application/json". Now that this content is up here, it means that we can make as many API calls as we want beneath it, and this is shared data for those calls. Okay, so we can go ahead and delete these two headers from the individual call, and let's continue with this call. (I can see another call up here, let's just delete this one above it, sorry. I'm just going to delete that.) Okay. So it's copied across all of this stuff here. We're going to deal with this shortly.

Let's change this call's Name to "Create text". We're going to be using this as an Action, and the difference between the Data and Action is that when we select Action, it then becomes available in our Workflow section. If it was set to Data, it would not become available as a workflow step. The Data one is for connecting data directly in, for instance, a data source like a Table or a Repeating Group, but we're running it as a Workflow step. We know that the data type is JSON because we set the option here.

Okay, let's come down to this section now. Let's look at this top to bottom. Firstly, it says the model is GPT 3.5 turbo. That's an outdated model, so we're not going to use it. We're going to highlight the "3.5-turbo" and type "4o". Not 4-0, but 4o. "O" stands for Omni. And now we have "role" is "system," "content" is "You are a helpful assistant." So this is where we're going to be dynamically feeding some instructions, and then beneath that, it says "role" is "user," and "content" is "Who won the world series in 2020?" It looks like we also have a set of instructions here.

So what we'll be doing is feeding through dynamic data in this section under system basically saying that "You are a social media manager and you're going to be summarizing this blog post into a social media post." Then in the role for the user, the content will be, "Well, here is the post that you need to summarize."

So we're going to now highlight with the quotation marks this content part here (the "You are a helpful assistant." part), and I want you to just copy it first to clipboard, command+C. After you've done that, we need to add the option to insert a dynamic value, and just above, we can see that Bubble gives us instructions: use the smaller than and greater than signs for dynamic values. So I'm going to do that here, I'm going to add two and then inside the <>, I'm going to type system_prompt.

And now, if I I click in this section, Bubble then says, "well, here is a key-value pair, and please paste in a temporary value." I'm going to paste that back in with the quotation marks: "You are a helpful assistant." Remember, we need the double quote marks here for it to work. This is going to not be private. If this box was checked, it would not show up in the Workflow section. We need it to show up in the Workflow section with a value so that we can insert dynamic data.

So now we're going to a similar thing for the row beneath that. We're going to highlight "Who won the world series in 2020?" with the quote marks and then copy it. Then replace it with smaller than and larger than signs. Then inside, we're going to type user_prompt. I'm going to paste that value back in below: "Who won the world series in 2020?" and then uncheck "Private" because we need to feed dynamic data into that value.

Okay, so some of this we don't need. So this is where you need to be ultra careful. I'm just going to click on row 19, and I'm going to delete from row 19 all the way to this comma. Okay, but I'm going to keep that curly brace on row 11. I'm going to scroll down like this up to row 19, press this backspace, and then click outside the box. So we've got user_prompt that ends with the greater than sign. Then we've got our closing curly brace, then we've got a square bracket, and then another curly brace.

Once you've done that, we can actually go ahead and initialize the call! Okay, so that was pretty quick. I'm going to have a look at the raw data, and we can see that we have our GPT-4o with today's date, and then we have "message," "role" is "assistant," and the "content," as "The Los Angeles Dodgers won the World Series in 2020..."

So we're sending dynamically-created system and user prompts to OpenAI. OpenAI will then follow the instructions from the two prompts to produce an output. The output, or the summarized Post, is then returned to us to save to the Bubble database.

Okay, let's go ahead and save that now, and let me show you a quick example in the Workflow tab (you don't need to do this, I'm just showing you where it turns up now), because it's an Action and because we unchecked "Private," under Plugins when you press "Click to add an action," we can see "OpenAI - Create Text". Here's our step, and here are the temporary values values that we inserted into the key-value pair in the API Connector: "You are a helpful assistant" - the user prompt - "Who on the world series in 2020?" and the pop-up that showed earlier with the results and the answer, that will then become available as the Result of step 3 to use in subsequent steps. Okay, I'm going to delete that action. We're not going to use it right now.

Now, in terms of the image, let's go back to the Plugins tab --> API Connector. Because we're going to add another call in a second, or we'll be using the import feature first and then editing the call. Let's head back to the documentation and we're going to scroll down to Image generation. So (reading from the OpenAI documentation): "Image generation", "Learn how to generate or manipulate images with DALL-E in the API... The image generation endpoint allows you to create an original image given a text prompt. When using DALL-E 3, images can have sizes of" these options here. We're going to be using the 1792x1024 pixels, so it's in more of a rectangular format. (Continuing to read from OpenAI documentation) "By default, images are generated at standard quality, but when using DALL-E 3, you can set quality:'hd'" - let's go ahead and do that as well.

Change this option to cURL if it's not already on that, and let's copy it. Let's hit back to Bubble. We're going to "Import another call from cURL", paste that in, Import, and let's work top to bottom. Let's call this one "Create image". It needs to be an Action, remember, we want it to show up in the Workflow section. Data type is JSON. It's a POST call, and there is a URL. We don't need these two headers because we already have these as shared headers for all of our calls. Okay, fantastic. Let's move on down here.

The model is "dall-e-3". For the prompt, this needs to be dynamic, okay, so let's highlight all of this, including the double quote marks. First, copy it to clipboard, and then use the smaller than and greater than signs. Let me type the word prompt. Okay, let's add this value back in next to "Value" below and uncheck "Private". So the size, let's change this to 1792x1024. Actually, I want to add the HD quality here, so I'm going to press comma, then enter or return, it's going to line up my cursor and then I'm going to say: "quality": "hd". Now, there is no comma after this because we're not adding any more lines.

Okay, so the prompt basically says, "DALLE-3, please generate an image of a white Siamese cat. I want that in a 1792 by 1024 aspect ratio, and I want the quality to be HD." Now, there are more parameters you can set here, feel free to look through the documentation to see what they mean and how to adapt them.

But for now, let's go ahead and initialize the call because there is something very interesting I want to show you in the results. Now, initializing this call does take a little bit longer, and while that is initializing, don't forget that you need to top up your OpenAI account for this call, for these initialization calls, to work, because what we're doing is we're actually making the call as we would. That's how we initialize calls in Bubble. We actually make the call, and that does cost money.

Okay, so we've got something returned here. Let's have a look at the raw data, and I can see a URL down here. I can see a URL. But this is interesting. This says, "revised_prompt" - "a white Siamese cat" was the data that we fed OpenAI, and it returned with a revised prompt: "A white Siamese cat with bright blue eyes, short sleek fur, and distinguished features..." So that's interesting, and this is a way for OpenAI to actually get better results because lazy prompting leads to lazy results and this revised prompt helps OpenAI and the DALL-E 3 model to create a much better image because it's got a much more detailed prompt. And OpenAI does this by default, very interesting.

Now, before you save this, this URL that's returned is currently text, and that makes sense. But if we want to save this URL, or this image, to the Bubble server, we actually need to change it to an image. Once it's set to "image," we will then be able to save this to the Bubble server from the workflow section. Okay, click on Save. And to expand on that slightly, OpenAI, the DALL-E 3 images are temporarily hosted, so they will one day go away. If we want them to persist, to be permanent, we need to save them back to Bubble, so that's why we're doing it.

Fantastic, so we now have our two API calls up and running, and we're ready for the Workflow section. Now, before we head to the Workflow part, just wanted to run over steps to make sure that the app you're currently working on is secure because you have Live keys in this app, so we need to make sure that it's set to private and that no one can access your developer Editor part of this app. Let's have a quick look.

I want you to head over to the Settings section and then go to General. I want you to make sure that your app is set to "Private app". (This setting defines who can see and modify the app, but doesn't prevent people from seeing it on the web in Live mode.) Make sure that your app is set to "Private app" and that you check the box that says "Limit access to this app in run mode with a username and password." My current username and password is both "rabbit" and "rabbit." If you have the username and password, this URL up here, and your app is not set to "Private app," then that becomes a definite security problem!

By having the username and password, it means that you can actually run this app as a user would. Okay, while we're here, we may as well just change the progress bar color, which I forgot to do earlier, just to black. Fantastic.

So let's quickly just summarize how far we've got now. So we've done the design part; we've done some basic Bubble workflows to create the Post and then set up our parameters; the right-hand Group Output has the three different states of idle, pending, and receiving the results; we've now initialized these OpenAI calls, one is for the text generation using the Chat Completions endpoint, and the other one is the Image generation using DALL-E 3. Let's now have a look at the workflows to make this work as it should.

So we're going to be running backend workflows now, but it's something that we need to enable in the API section in the Settings tab. So click on the Settings tab, then let's go to the API. Now we're going to check "Enable Workflow API and backend workflows." With that checked, when we click on this dropdown, we can now see that we have "Backend workflows". Let's click that, and here we are in the backend workflow section.

Now, these work a little bit different to the workflows that you've run so far, as this is now running on the server. The Workflow tab are for workflows that run client-side in the user's browser, in your own browser. Okay, so the way we work within the backend workflow section is we have to create what's called an endpoint, and then to be able to access this endpoint, we have to schedule it to run as an API workflow in the Workflow tab in the browser or on the client side. That sounds like a bit of a mouthful, so we're going to walk through the steps of how backend workflows and frontend workflows interact.

What we first need to do is click on this block that says, "Click here to add a backend workflow..." and we're going to choose "New API workflow". We need to give it a name. We're going to call it, "create-post", all lowercase, and it needs to be a single string, so joined with a hyphen. Now, we don't need to expose this as a public API workflow, so we're going to uncheck that.

Now, the next step is to add new parameters! So we're going to click this box. We're going to give it the key of "post," and the type is a data type Post. So what this means is the endpoint will be expecting Post data, and that is created client-side. In the user's browser, when they click on Generate Post, the Post is created by Create a thing, create a Post. They're going to take that Post, feed it through to this backend workflow via this parameter. And now we can access the Post and use it to interact with OpenAI!

So with that done, we're going to press "Click here to add an action" and we're going to go down to our Plugins section and choose "OpenAI - Create text". Okay. So now we have our system_prompt and our user_prompt, and the user_prompt will be the exact instruction and the system_prompt will add a bit of context: what is OpenAI acting as? And we're going to say that you are a social media manager whose job it is to summarize blog posts into social media posts, and then we'll feed the Post through in the user_prompt.

So I'd like you to do the following. In the system_prompt section, let's insert dynamic data. I want you to type a-r-b. This is going to bring up Arbitrary text as a data source, and basically this allows us to construct paragraph text. I'd like you to type the following: "You are a social media manager whose job it is to summarize blog posts into engaging social media posts." And that's it. That's going to be our system prompt. So basically OpenAI is our social media manager, and they are tasked with this particular job that we're going to instruct it to do next.

Now, after you finish typing that, please close, and then I want you to type the word JSON, j-s-o-n, and select ":formatted as JSON-safe". While I'm hovering on ":formatted as JSON-safe", I can see the Bubble is giving me a little hint to say, "if you'd like to learn more about this operator, see this reference". I'm going to click on that. Let's see what this actually does. So it says "This operator sanitizes a text string, date, yes/no, or list of texts into a JSON-acceptable format by escaping characters". So when it comes to paragraph text, we might have certain characters that aren't going to work in the JSON format. So ":formatted as JSON-safe" is a way to make sure that we're sending sanitized data to OpenAI to be able to interpret correctly. So we always need JSON-safe. Let's go back to Bubble.

I'm going to click back on "Arbitrary text", I'm going to access the "More" option, I'm going to type "json" and then say ":formatted as JSON-safe." We're going to do a similar thing for the user_prompt now. So, we're going to insert dynamic data, type a-r-b to get arbitrary text because we need to construct our prompt. It's going to be paragraph text - some of it will be static, some of it will be dynamic. Now I've pasted this in, and I'm going to read it for you. First, we gave the system prompt saying, "You're a social media manager..." now we have the user_prompt, and it says, "Write a post that teases the below context in no more than [post length] Length characters in a [post tone] tone," here's the "Context [Source]," and that is going to be the actual post itself, the long-form post about the Cybertruck, and then beneath that, it says, "The post is." So this is just a short lesson on prompt construction, but feel free to go back to the documentation in OpenAI and look at their particular methods for prompt construction, because this is where it came from.

Okay, so we need to add some dynamic data here: the post length and the post tone. We can get that from this particular parameter (post). The post has been fed through this parameter and is made available to us now. So I'm going to highlight with the brackets, [post length], and we're going to feed through the post length here. So "Insert dynamic data", the data source is the post, and it's going to be the Platform, because the user has the option to select Facebook, LinkedIn, or X.com, and they all have variable post lengths. Here is the length here (post's Platform's Length). So that takes care of the length. Now we're going to set the tone. Similar thing, highlight brackets plus "post tone", I'm going to access the Post, find the tone, and it would be just the Display (post's Tone's Display).

And now we need to feed through the Source. So this is the blog post itself. Just going to make sure I get those brackets, square brackets, "Insert dynamic data," select the post, and then find the Source (post's Source). Now it says "the post is" - we'll leave that blank because that is basically given instruction to just return the post itself with nothing else. I'm going to close that, and then I'm going to choose ":formatted as JSON-safe".

Okay, and because this is a POST request, something will be returned, okay, which means we can add another step here, go to "Data (Things)" --> "Make changes to thing..." Now we can select the post so we can save the results of this POST request, and the result will be saved to the Output. Because this is a POST request, we're sending data and then we're receiving data, and we receive it by referencing the "Result of step 1 (OpenAI - Create text)'s choices:first item's message content." And you'll become familiar with this particular expression or format as we continue on with this course. So it's the choices', first item's, message content. And by the way, this is actually illustrated to us in the API section. Let's have a quick look.

I'm going to go back down to the "Create text," and I'm going to click on "Manually enter API response," because I can see the format that's returned here. So it was OpenAI --> choices, and then this is a List, so it was the first item's, message content. Here it is here, the message. Okay, and it's at this stage that we can change the Status to Complete. Status equals Complete.

So let's head over to the index page, please, and then open up the Design, click on Button Generate post, and let's edit the workflow. So what I'm going to do is add a new step 2. Now we can go down to Custom Events and select "Schedule API Workflow," because now we need to send data to the backend workflow section because we're currently in the client's browser.

So we're going to select the API workflow we created. It's called "create-post." We're going to feed through the Post from the Result of step 1, and then we're going to set the Scheduled date by typing the word "date" and then selecting "Current date/time." Scheduled date meaning that we can actually delay sending this or scheduling this API workflow, but we want it done in real-time, so current date and time takes care of that.

Let's have a quick look at our "Create a new post..." step 1 again. We capture the dropdown for the Platform, we capture the dropdown for the Tone, we've got the Source being the MultilineInput Source's value, and the Status is Pending. Guys, we can actually go ahead and test this now. I'm going to preview the page.

Okay, let's create our first post! I'm going to select the platform being X.com just because it will be the most summarized result. I'm going to select the tone being... let's start with "Inspirational." And now I'm going to go find that blog post about the Cybertruck. So here is a Cybertruck review, and I'm just going I'm going to go ahead and quickly copy this text.

Okay, going back to Bubble. I'm going to paste in this blog post. Scrolling up, making sure everything looks good, and by the way, you can use any blog post you like here, I'm just using the Cybertruck, you can go ahead and find any post on any topic you like. I'm going to generate the post. Generation in progress. We can see the post is in the URL, and here's the result, folks.

So remember, we had dynamic data in the subtitle? "Here's your X.com post" because we selected X.com and saved it to the database "with an inspirational tone. Generate an image with the button below." I don't see the image, but we're going to deal with that shortly. And the result is: "...Meet the Tesla Cybertruck, not just a truck, but a revolution on wheels," etc. etc. And, how helpful is this? We have a little link emoji and we can actually edit this post to add the link because it's in a MultilineInput. I'm going to go ahead and save this post.

Let's go have a look at the database next. Head over to the Data tab, App data, and here is our post here. So here is the Output that was just generated. "Output edited" is what I saved, and I could have included the original blog post link here. The Source is the original blog post, Tone is "Inspirational", Platform is "X.com", and the Status is "Complete". This is absolutely perfect.

Let's just quickly head over to the Design tab to look at some of the conditionality. Okay, I'm going to click on Group Output, and I just want to see why the "Button Generate image" isn't there. So that would be... let me expand this out a bit. Group Results, and expand Group Results, and here is the button I'm looking for. Okay. Aha. So we don't have the conditional statement here, so we need a conditional. Define another condition that says "When Get data from page URL [the parameter is post, which is a type Post]'s image is empty," that's when this is visible. Check.

Let's have a look at "Image Post". This needs a condition as well, which we'll say, "When Get data from page URL [which is post, this is all case sensitive as well, folks]'s image is not empty" this time, the opposite of the button, that's when it's visible.

Okay, let's preview this. Going to refresh the page and here we have our button. Fantastic. So now we need to go to the backend workflows again, and we're going to work on creating the prompt to generate this image. And this is going to actually be a daisy-chained multi-step approach because we can get really creative with how we use the Chat Completions API. I want to demonstrate this to you.

So let's go back to the backend workflows, and I want you to create a new API workflow. Let's call this "create-image". We're not exposing this as a public API workflow. We need a parameter, which is a post, and that is of type Post, and it's the same as the "create-post" endpoint, where we are feeding a post through, where the "create-post" endpoint, we're using the source being the long blog post, now that we have the summarized result, social media post for X.com in an inspirational tone, we're going to now use that to look at prompt construction for the best image possible.

So we're going to set up an effective prompting technique here with a three-step daisy-chained process. In the first prompt, we'll be asking OpenAI to help us come up with the stock image Concept for the Post. For instance, if the post is about the Tesla Cybertruck, well, how can we best depict this image? Is it just a Cybertruck on a plain background? Is it a Cybertruck in a city? If so, which city? There's so many variables, so we're going to let OpenAI come up with the best Concept for us.

Now that we have a stock image Concept, we're going to ask OpenAI to please create the best possible prompt to represent this new Concept. OpenAI can probably do a much better job than a human can, so let's take advantage of that.

Lastly, now that we have the prompt constructed, we're going to send it to DALL-E for the image creation and then save the image back to the original post, and update the Status to Complete.

So let's add a step here. This will be in Plugins, and we're going to click "OpenAI - Create text" first, and I'm going to re-label this to "OpenAI - Create idea." Okay, now we're going to be using the same system_prompt from create-post, so I'm going to go grab that and then paste that in.

Now, in terms of the user_prompt, here is the context. I'm going to copy and paste what I have in my notes. I'm going to choose arbitrary text, actually, we need arbitrary text here as well. Let's just quickly deal with that. So on the "Create text" for "create-post," we're just going to copy this expression, go back to "create-image," and then in here, just paste this expression. Okay, so we can do that for future workflow steps as well.

Let's go back to the user_prompt for "Create idea." So this is the prompt, and you can find this prompt in the description below this video. The prompt is: "Create a short idea for a stock photo that would pair well with the below social post." Okay, then a bit more about the construction... "[subject] [in relation to] [a background]", and then we need to feed through the dynamic data to say, here is the social media post that has already been created by OpenAI, and give me the stock image idea.

Okay, so we need to feed through dynamic data here for the post's output and this is going to be "post's Output". Okay, post's Output. Let's close that and then format as JSON-safe. And I just want to show you the database again quickly. So the post Output is here, this is what was created in step number one. So that's what we're feeding through the post's Output. So that's step number one.

Step number two is take the idea and now create the best possible prompt. You, AI, you're better at prompt creating than I am, so let's work at this together. You create the prompt for me, I'm just going to give you the instruction.

So we're going to go through the same step again. "Plugins" --> "OpenAI - Create text", I'm going to rename this to "Open AI - Create prompt." Okay, I'm going to copy across the system_prompt. Insert dynamic data, right-click and paste that expression in. And you might be thinking to yourself, well, we're just using the same system prompt throughout, maybe it didn't need to be dynamic in the API Connector. But actually, this gives us a lot of flexibility because we could start adapting the system_prompt and the context through these various steps. So it's good to keep it as dynamic data. But, for this course, we're just going to be using the same system_prompt, but rather, changing the user_prompt. But it's good to have both been dynamic, to give us a lot of flexibility.

Now for the user_prompt, insert dynamic data, arbitrary text, because we're dealing with paragraph text that is both static and dynamic. I'm going to go and find this prompt that I've saved and paste it in here. Okay, again, you can copy and paste this prompt from the description area below.

So this one's slightly different. Now it says, okay, OpenAI, please "Create a prompt to be used for image generation for the idea below" where the idea is created in step 1, "Use precise visual descriptions (rather than metaphorical concepts). Try to keep the prompt short yet precise." Now, I've got the Prompt Structure. Lastly, I've got that I've said "The photo composition should be simple." And the idea will be the Result of step 1, It's created in step 1, so let's feed that through! And you recognize the actual formatting here, which would be "Result of step 1 (OpenAI - Create idea)'s choices:first item's message content". And then "The prompt is:".

And there is no right way or wrong way for prompt construction, I'm just trying to give you an idea of how detailed we can get to get the best possible result here. Let's close that and let's say ":formatted as JSON-safe".

Okay, so the conceptual stuff is done! We've now got a prompt, and we can now send this prompt through to DALL-E! So for that, we're going to add a new step and go to "Plugins" --> "OpenAI - Create image", and this one's a simple one. All we need to do is point to the prompt that is outputted from step 2. Insert dynamic data, now we choose the result of step 2, that's where the prompt has been generated for us. So, it's "Result of step 2 (OpenAI - Create prompt)'s choices:first item's message content:formatted as JSON-safe". We want it formatted as JSON-safe because OpenAI is going to return paragraph text for what the prompt is.

Lastly, guys, we can now go to "Data (Things)" and then make changes to the post.
"Thing to change" is the post, and we can finally set the Image and set the Status. Status is an easy one, now we are Complete, and now for the Image. So the Image is coming from the Result of step 3. We've sent a POST request to the DALL-E 3 model with a prompt. That image has been constructed, and DALL-E is now returning a link to that image. So that is the result of step 3. It's "Result of step 3 (OpenAI - Create image)'s data:first item's url." We remember this from the API Connector when we changed the format from text to image, and because we did change it to an image, we now have these new operators here. I'm going to choose/add ":saved to S3" at the end of that expression. Now that has to be done in order to save that image to the Bubble servers. Otherwise, it will stay and eventually expire on the OpenAI servers. We're not going to add a new file name, we don't need to.

So that was pretty much a deep dive into prompt construction and how we can and get a lot smarter with leveraging multiple different endpoints of OpenAI, or multiple different models, to give us the best possible result. Because I think that OpenAI and AI in general is just better at conceptualizing ideas, creating prompts, and so forth.

Let's head back to index page, please, and let's bring up our button. I'm just going to search for a button up here and find the Generate image button. There it is there, and let's add a workflow. So now we can go to "Custom Events" --> "Schedule an API Workflow". This time, we're going to schedule the "create-image" workflow, feed through the post, which is basically the Parent group's Post, and the Current date/time. And what we could also do is, I guess this would be optional, but we could reset the Status of the post to Pending so it says "Generation in progress." Maybe this is a good idea just to prove that something is happening here, I just need you to be patient, please, as a user, because this is a multi-step process now. So although the Chat Completions API is quite fast, it is going through the two steps and then it's feeding through DALL-E, which is a little bit slower. So let's do this.

Let's say, "Make changes to a thing...", where Thing is the Parent group's Post, and just change the Status back to Pending, just for now. This is just optional, and now we're just talking about UX.

Okay, I think we're ready to run this! So I can refresh, this data will remain here because we have this URL parameter post=, and that is the unique ID in the database of the Post we're dealing with.

I'm going to now generate an image. Let's see what happens. So back to "Generation in progress." And there we go, guys. There is our Tesla Cybertruck. We can now save this post again, and let's have a look at the data.

Head over to the Data tab. Let's open and then let's just refresh the data because now, if I click on edit icon (pencil), I can now see this, and there's the beautiful Cybertruck. It allows me to zoom in slightly as well.

Okay, folks, we're done! We've got our result. We took a blog post, we selected X.com as a platform, we selected Inspirational tone, we then got the social media post that we're looking for, and then used that post to then generate this particular image. Now, of course, there are many options you can go to from here, such as regenerate the image or start again, regenerate the post... I'll leave that up to you to explore. But I hope you enjoyed this intro to AI with Bubble. Super, super powerful what we can do, and I look forward to seeing what you create from here!

Prompts

Create text:

System_prompt: You are a social media manager whose job it is to summarize blog posts into engaging social media posts.

User_prompt: Write a post that teases the below context in no more than [post's Platform's Length] Length characters in a [post's Tone's Display] tone

Context: [post's Source]

The post is:

Create idea:

User_prompt: Create a short idea for a stock photo that would pair well with the below social post.

The idea template (should be a simple composition): A [subject] [in relation to] [a background]

The post: [post's Output]

The stock image idea is:

Create prompt:

User_prompt: Create a prompt to be used for image generation for the below idea. Use precise, visual descriptions (rather than metaphorical concepts).

Try to keep the prompt short, yet precise.

Prompt Structure:

“A photo of [subject], [subject’s characteristics], [relation to background] [background]. [Details of background] [Interactions with color and lighting]. Taken on [Details of camera, lens, style and settings].”

The photo composition should be simple.

The Idea: [Result of step 1 (OpenAI - Create idea)'s choices:first item's message content]

The prompt is:

Did this answer your question?