Using Machine Learning to Bring Your Stories to Life (with OpenAI and Next.js)
For this blog post, we'll make a few assumptions before continuing, but you should ideally have:
- An Upstash account where you have a Redis and QStash instance created
- An OpenAI account with access to your API key
- A Next.js project where we'll create the story generator functionality
- A Vercel account to deploy your project to
Introduction
Have you ever wanted to generate your own stories using AI? With OpenAI's completions API and Upstash's QStash and Redis, it's now easier than ever to create your own custom stories using natural language processing. In this tutorial, we'll walk through the process of setting up and using these tools to generate unique and engaging stories.
View more images of the app:
Architecture
You're likely to get a decent understanding of how the app is setup from looking through the code, but as a bit of a higher level overview, there is the image below showing some parts of the application flow and how they communicate.
Project Setup
First up we'll want to create a Next.js project. This can be done by running the following to create a new Next.js project with TypeScript. You can find out the steps to setup Next.js here.
For the sake of this tutorial, we also have Tailwind CSS (forms and typography too) installed, but that's totally optional and only for the frontend form styling.
Next up we'll want to install Upstash's QStash and Redis libraries via the following:
You'll now want to create a .env.local
file and populate it with the following keys (and the values from the relevant places).
You can find your QStash and Redis tokens in the Upstash console, the OpenAI API key here, and your site URL in your Vercel dashboard once you have a project created and a basic Next.js project deployed.
Frontend Setup
Next up we'll create the page and form for inputting the story prompt. You'll need a text field for the prompt and a submit button.
Story Creation
File: pages/index.tsx
This file defines a React component that displays a form that allows users to input a theme, character, and moral for a story. When the form is submitted, it sends a POST request to the /api/create
endpoint with the inputted theme, character and moral values as the body.
The component then enters a polling state where it sends a GET request to the /api/poll
endpoint every second along with the message identifier received in the previous story creation request, which allows us to track the story creation request to the story we're polling for to check when it has finished being generated by OpenAI.
When the response from the /api/poll
endpoint contains a choices property, we know that the polling request has returned a successfully generated story, so the component stops polling and displays the story text by splitting it into paragraphs and rendering each paragraph separately.
Interval Hook
File: hooks/useInterval.ts
The useInterval
hook makes use of the useEffect
and useRef
hooks to manage the interval and the callback function that works seamlessly with the React component lifecycle, as well as providing a convenient way to manage the interval and callback within a React component, optimizing performance and making the codebase that little bit more maintainable. You can find more information on this hook here and here.
API Setup
First up we'll create the callback, poll and create files, as well as the Redis and QStash library usage.
Story Creation
File: pages/api/create.ts
We first check that the request method is POST
, and sends a response with a status code of 400 (indicating a client error) if it is not. We then proceed to destructure the theme, character, and moral fields from the body of the request.
Next up we call the publishJSON
method on the qstashClient
object, which sends a POST
request to the OpenAI API with a JSON body containing a prompt to generate a children's story based on the values of theme, character, and moral. It also sets several headers, including an authorization header with a token stored in the QSTASH_TOKEN
environment variable, and a forwarded authorization header for passing through the OPENAI_API_KEY
which will be used alongside the OpenAI API request.
We then return the message ID of the request if the publishJSON
call is successful, which will be used for polling to check when the request is finished. If an error occurs, it sends a response with a status code of 500 (indicating an internal server error) and the relevant error message.
Callback
File: pages/api/callback.ts
First off, we first try to decode the body of the incoming request, which will be a base64-encoded string, and if successful, it stores the decoded string in Redis under the same key as what was returned when we sent the initial request to QStash.
Finally, we send return a response with a status code of 200 (indicating success) as well as the decoded string. If any errors occur, we will return a response with a status code of 500 (indicating an internal server error) and the error message.
Polling
File: pages/api/poll.ts
First up, we destructure the id
from the query object of the request. We then try to retrieve the data stored in Redis under the destructured id
and if no data is found, it sends a response with a status code of 404 (indicating that the requested resource could not be found) and a message stating so.
If data is found belonging to the given key, it sends a response with a status code of 200 (indicating success), and the found data along with it. If any error occurs, we return a response with a status code of 500 (indicating an internal server error) and the relevant error message.
Libs
Next up, we'll create two files for creating the QStash and Redis clients, which are used within the story generation process. Both files export an object that is used to interact with the respective external service.
File: lib/qstash.ts
The QStash client is initialized with a token stored in the QSTASH_TOKEN
environment variable. This object can be used to send HTTP requests to the Upstash QStash service.
File: lib/redis.ts
The Redis client is initialized with a URL and token stored in the UPSTASH_REDIS_REST_URL
and UPSTASH_REDIS_REST_TOKEN
environment variables, respectively. This object can be used to store and retrieve data in a Redis database through the Upstash Redis REST API.
Conclusion
With OpenAI's completions API, as well as Upstash's QStash and Redis, it's easy to generate custom stories using natural language processing. By following this tutorial, you should now be able to set up your own system for generating stories using these tools, and make your own changes and improvements upon it.
You can view the source code in its entirety here.
Further Improvements
Below are a few ideas on what you could do next, using this story generator as a start:
- Update the frontend styling to be a lot more visually appealing and colourful
- Add Dall-E image generation using OpenAI to the stories based on a given prompt
- Hook the output up to a book printing service via an API, so users can order physical books
There are so many possibilities and directions you could take this, so have fun and enjoy the process. You can even use the work so far as a base for other projects that could make use of OpenAI, QStash and Redis.