How to build an AI-powered Reality TV Scenario Generator App in Next.js with Replicate + LaunchDarkly featured image

If you’ve met me, it's no secret that I’m a massive fan of Reality TV. Although I would never want to participate in a show, I feel like I’d be an absolutely wonderful director of Reality TV in another life.

Thanks to the power of technology and AI, we can try our hand at a Reality TV Simulator app.   Let’s build an AI-powered application that generates scenarios for a wilderness survival reality TV show, similar to a personal favorite, "Naked and Afraid." Let’s use Next.js, LaunchDarkly, and Replicate to create a Reality TV AI generator application. 

(PS, really digging this fun?  Stay tuned for part two on how to add ArcJet to the party to rate limit your API requests)
Without further ado, survivalists — ergh… developers, let's begin. 

What tech stack powers this?

Next.js: A React framework for building full-stack web applications. It provides features like server-side rendering, static site generation, and API routes, making it ideal for our application.

LaunchDarkly: A feature management platform that allows us to control feature rollouts, A/B testing, dynamic configuration, and run experiments without redeploying our application.

Replicate: A tool that allows you to run AI through an API, providing a way to run machine learning models as part of your app's backend. It’s great for adding AI features like image generation, text summarization, or other complex tasks.

Requirements

You could also create the end result using another LLM API, such as OpenAI, Anthropic, Hugging Face, or Gemini; however, this tutorial will be built using Replicate’s API library.

Setting up the project

Clone this example repository and install your requirements.

git clone https://github.com/erinmikailstaples/Reality-TV-Generator
cd celebrity-wilderness-challenge
npm install 

Navigate to your “.env.local.example” file, rename it to “.env.local” and be prepared to add in your Replicate API token.

Get started with Replicate

The Replicate SDK is already installed within our application and listed as a dependency; however, we need an API key from Replicate to get started.

First, log in or create an account on Replicate.

If logging in for the first time, select “Run a model.”

Then, grab your API key from the modal on the side.

If you’ve already created an account on Replicate, the steps will slightly differ. You’ll first log in to your account, click on your username in the upper left-hand corner, and select “API tokens.”

Then, create a new API token by giving it a name and selecting “Create token”.   I’ve named it “Reality-TV-Application.”

Copy your token and place it within the .env.local file.
Let's test the app is running before moving on to the next step.

Within the terminal, run “npm run dev” and then navigate to localhost:3000. 

You should be greeted with a Celebrity Wilderness Challenge Scenario Generator screen. 

Test that you’ve got the Replicate token input correctly by selecting a celebrity and a location and seeing if it returns a silly wilderness survival scenario.

Excellent, silly scenario in tow — we’re ready to continue onto the next step. 😈

Optimizing the app

Now, we could leave it here. Our app is technically functional and does a pretty alright job of creating faux reality TV scenarios.

However, this limits us in two ways: We can only easily adjust our prompt or model by adjusting the code and redeploying, annoying!

This limits the scalability and safety of this application. Let’s explore how we can use LaunchDarkly to test out different prompts and models quickly.

First, let's implement LaunchDarkly JSON feature flags to control our prompts.

Configure LaunchDarkly

First, install the LaunchDarkly React Client SDK

Within your project folder — run the following.

npm install launchdarkly-react-client-sdk

Create a LaunchDarkly provider component 

Create a new folder under the src directory. Name it “components” 

Then, create a file within this folder titled LaunchDarklyProvider.tsx (file path should be src/components/LaunchDarklyProvider.tsx). Add the following content to that file.

npm install "use client";
import { withLDProvider } from 'launchdarkly-react-client-sdk';


function LaunchDarklyProvider({ children }: { children: React.ReactNode }) {
 return <>{children}</>;
}


export default withLDProvider({
 clientSideID: process.env.NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_SIDE_ID!,
 options: {
   bootstrap: 'localStorage'
 }
})(LaunchDarklyProvider as React.ComponentType<{}>);launchdarkly-react-client-sdk

Update your layout component

Replace the current layout.tsx component, with the component below:

import type { Metadata } from "next";
import localFont from "next/font/local";
import "./globals.css";
import dynamic from 'next/dynamic';


const geistSans = localFont({
 src: "./fonts/GeistVF.woff",
 variable: "--font-geist-sans",
 weight: "100 900",
});
const geistMono = localFont({
 src: "./fonts/GeistMonoVF.woff",
 variable: "--font-geist-mono",
 weight: "100 900",
});


export const metadata: Metadata = {
 title: "Celebrity Wilderness Challenge Simulator",
 description: "Generate hilarious survival scenarios for celebrities in bizarre environments",
};


const LaunchDarklyProvider = dynamic(() => import('../components/LaunchDarklyProvider'), { ssr: false });


export default function RootLayout({
 children,
}: Readonly<{
 children: React.ReactNode;
}>) {
 return (
   <html lang="en">
     <body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
       <LaunchDarklyProvider>
         {children}
       </LaunchDarklyProvider>
     </body>
   </html>
 );
}

Update the home component:

Modify your src/app/page.tsx file to use LaunchDarkly flags by replacing it with the below:

"use client";
import React, { useState } from "react";
import { useFlags, useLDClient } from "launchdarkly-react-client-sdk";


export default function Home() {
 const [celebrity, setCelebrity] = useState("");
 const [environment, setEnvironment] = useState("");
 const [scenario, setScenario] = useState("");
 const [isLoading, setIsLoading] = useState(false);
 const flags = useFlags();
 const ldClient = useLDClient();
  const generateScenario = async () => {
   setIsLoading(true);
   try {
     const user = {
       key: 'user-key-123',
       custom: {
         celebrity: celebrity,
         environment: environment
       }
     };
     await ldClient?.identify(user);
      console.log("Sending request with:", { celebrity, environment });
      const response = await fetch("/api/generate-scenario", {
       method: "POST",
       headers: { "Content-Type": "application/json" },
       body: JSON.stringify({ celebrity, environment, promptConfig: flags.aiPromptConfig }),
     });
      if (!response.ok) {
       throw new Error(`HTTP error! status: ${response.status}`);
     }
      const data = await response.json();
     console.log("Received response:", data);
      if (data.error) {
       throw new Error(data.error);
     }
      setScenario(data.scenario);
   } catch (error) {
     console.error("Failed to generate scenario:", error);
     setScenario("Failed to generate scenario. Please try again.");
   } finally {
     setIsLoading(false);
   }
 }




 const celebrities = [
   "Nicolas Cage", "Bad Bunny", "King Charles", "Justin Bieber", "Lady Gaga", "Snoop Dogg", "Martha Stewart", "Kanye West",
   "Betty White", "Gordon Ramsay", "Beyoncé", "Jeff Goldblum", "Dolly Parton",
   "Bill Nye the Science Guy", "Flavor Flav", "The Rock's Eyebrow", "Chuck Norris", "Weird Al Yankovic"
 ];


 const environments = [
   "Inside a Giant Burrito", "Bjork Hive Mind", "just Twitter", "The White House", "Underwater Disco", "Haunted IKEA", "Jurassic Park Gift Shop",
   "Sentient Cloud City", "Chocolate Factory Gone Wrong", "Upside-Down Skyscraper",
   "Abandoned Theme Park on Mars", "Inside a Giant's Pocket", "Miniature Golf Course Jungle",
   "Intergalactic Space Truck Stop", "Zombie-Infested Shopping Mall",
   "Enchanted Forest of Talking Furniture", "Post-Apocalyptic Ball Pit",
   "Dimension Where Everything is Made of Cheese"
 ];


 return (
   <div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)] bg-white text-gray-800">
     <main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
       <h1 className="text-2xl font-bold text-gray-900">Celebrity Wilderness Challenge Simulator</h1>
       <div className="flex flex-col gap-4">
         <select
           value={celebrity}
           onChange={(e) => setCelebrity(e.target.value)}
           className="p-2 border rounded"
           aria-label="Select celebrity"
           disabled={isLoading}
         >
           <option value="">Select a celebrity</option>
           {celebrities.map((celeb) => (
             <option key={celeb} value={celeb}>{celeb}</option>
           ))}
         </select>
         <select
           value={environment}
           onChange={(e) => setEnvironment(e.target.value)}
           className="p-2 border rounded"
           aria-label="Select environment"
           disabled={isLoading}
         >
           <option value="">Select an environment</option>
           {environments.map((env) => (
             <option key={env} value={env}>{env}</option>
           ))}
         </select>
         <button
           onClick={generateScenario}
           disabled={!celebrity || !environment || isLoading}
           className="p-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:bg-gray-300"
         >
           {isLoading ? 'Generating...' : 'Generate Scenario'}
         </button>
       </div>
       {isLoading && (
         <div className="mt-4 p-4 bg-gray-100 rounded text-gray-800">
           <p className="text-center">Generating your hilarious scenario...</p>
           <div className="loader mt-2"></div>
         </div>
       )}
       {!isLoading && scenario && (
         <div className="mt-4 p-4 bg-gray-100 rounded text-gray-800">
           <h2 className="text-xl font-semibold mb-2 text-gray-900">Survival Scenario:</h2>
           <p>{scenario}</p>
         </div>
       )}
     </main>
   </div>
 );
}

Update the API Routes

Modify your src/app/api/generate-scenario/route.js file to use the LaunchDarkly flags:

import { NextResponse } from "next/server";
import Replicate from "replicate";


const replicate = new Replicate({
 auth: process.env.REPLICATE_API_TOKEN,
});


export const dynamic = 'force-dynamic'; // Add this line


export async function POST(req) {
 try {
   const { celebrity, environment, promptConfig } = await req.json();
    console.log("Received request with:", { celebrity, environment, promptConfig });
    const prompt = promptConfig?.prompt
     ? promptConfig.prompt.replace('{celebrity}', celebrity).replace('{environment}', environment)
     : `Generate a survival scenario in 500 characters or less for ${celebrity} in ${environment}.`;
    console.log("Generated prompt:", prompt);


   const output = await replicate.run(
     promptConfig?.model || "replicate/llama-2-70b-chat:2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1",
     { input: { prompt } }
   );


   console.log("AI output:", output);


   return NextResponse.json({ scenario: output.join("") });
 } catch (error) {
   console.error("Server error:", error);
   return NextResponse.json({ error: "Internal Server Error" }, { status: 500 });
 }
}

Update your Next.js Configuration

Update your next.config.mjs file to handle LaunchDarkly dependencies:

/** @type {import('next').NextConfig} */
const nextConfig = {
 reactStrictMode: true,
 webpack: (config, { isServer }) => {
   if (!isServer) {
     config.resolve.fallback = {
       ...config.resolve.fallback,
       net: false,
       tls: false,
       fs: false,
     };
   }
   return config;
 },
};


export default nextConfig;

Connecting the LaunchDarkly App to the code 

Log in to your LaunchDarkly account. Create a new project, give it a name you’ll remember fondly.

Update your environment variables

In your .env.local file, uncomment the LaunchDarkly variable and add your LaunchDarkly client-side ID.

You can find that by selecting the three dots next to your environment and selecting client-side ID. 

Note: these are environment-specific keys. Ensure you’re in the right environment when selecting your key and making changes. 

NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_SIDE_ID=your_client_side_id_here
REPLICATE_API_TOKEN=your_replicate_api_token_here

Creating a JSON flag

Now, let’s create a flag to adjust your prompt and model. 

Create a new JSON feature flag called aiPromptConfig with the following details.

Name: aiPromptConfig

Flag Configuration: Custom

Permanent: No

Flag Type: JSON

Variations:

Name: llama-2, prompt standard

Value:
{

  "model": "replicate/llama-2-70b-chat:2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1",

  "prompt": "Generate a hilarious and absurd survival scenario in 500 characters or less for {celebrity} in {environment}. Include unexpected twists and comical challenges."

}

Name: llama-2, prompt spooky

Value:
{

  "model": "replicate/llama-2-70b-chat:2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1",

  "prompt": "Generate a very spooky and scary survival scenario in 500 characters or less for {celebrity} in {environment}. Include unexpected twists and comical challenges. Add ghost emojis as well."

}

Check the “Use LD Client Side ID” box and then select “save.

Turning the flag on 

Navigate to your flag screen and turn the flag on. Let’s set it so that we serve a spookier variation when our flag is on. Note that in the spookier variation, we asked them to show some emojis to let us know that we’re serving the right prompt.

Look at that haunting response!  Our prompt has changed without redeploying our application at all!  Wahooooo! 

Feel free to mix things up with different models, prompts, or even add rules as you see fit for added fun and flair.

Congratulations, you’re a reality TV mastermind! 

You've just created a celebrity survival simulator that would make Bear Grylls proud! LaunchDarkly and Replicate joined forces to create chaos like Nicolas Cage in a giant burrito.

Together, we explored how to swap between different prompts faster than Snoop Dogg drops beats, no redeploy needed.😉

If you enjoyed this tutorial, you may also like:

Conclusion

You've mastered celebrity survival sims faster than you can say "The Rock's Eyebrow in a Haunted IKEA." For more wild adventures (or to geek out on the latest season of Naked and Afraid), reach out to me at emikail@launchdarkly.com, the site formerly known as Twitter, Mastodon, or the LaunchDarkly Discord. 

Remember, the sky's the limit (unless you're in an upside-down skyscraper)!
Stay tuned for part two of this tutorial, which will explain how to rate limit your API requests using ArcJet.

Like what you read?
Get a demo
Related Content

More about Feature Flags

October 3, 2024