├── .eslintrc.json ├── .gitignore ├── README.md ├── assets └── images │ └── main_image.jpg ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── _app.tsx ├── _document.tsx ├── api │ └── cringe.ts └── index.tsx ├── public ├── favicon.ico ├── next.svg ├── thirteen.svg └── vercel.svg ├── styles ├── Home.module.css └── globals.css └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AI Cringe Generator powered by GPT-3 2 | 3 | Full-stack application built with NextJS, API routes, and OpenAI's GPT-3 API. 4 | 5 | Watch the course here: https://www.youtube.com/watch?v=5i1Q2GSqidU 6 | 7 | To run the app, you need to add a `.env.local` file to the project root and add your [OpenAI API key](https://openai.com/api/) under `OPENAI_API_KEY`. 8 | 9 | ![thumbnail](https://user-images.githubusercontent.com/52977034/213849053-ae94b66a-5989-46e1-94ad-371312e473b1.png) 10 | -------------------------------------------------------------------------------- /assets/images/main_image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codinginflow/nextjs-gpt3/b0dcfe026ebce79b9bf48054af46b83f55c8a282/assets/images/main_image.jpg -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | } 5 | 6 | module.exports = nextConfig 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-gpt3", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@next/font": "13.1.2", 13 | "@types/node": "18.11.18", 14 | "@types/react": "18.0.27", 15 | "@types/react-dom": "18.0.10", 16 | "bootstrap": "^5.2.3", 17 | "eslint": "8.32.0", 18 | "eslint-config-next": "13.1.2", 19 | "next": "13.1.2", 20 | "openai": "^3.1.0", 21 | "react": "18.2.0", 22 | "react-bootstrap": "^2.7.0", 23 | "react-dom": "18.2.0", 24 | "typescript": "4.9.4" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import 'bootstrap/dist/css/bootstrap.min.css'; 2 | import '@/styles/globals.css' 3 | import type { AppProps } from 'next/app' 4 | 5 | export default function App({ Component, pageProps }: AppProps) { 6 | return 7 | } 8 | -------------------------------------------------------------------------------- /pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from 'next/document' 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /pages/api/cringe.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import type { NextApiRequest, NextApiResponse } from 'next' 3 | import { Configuration, OpenAIApi } from 'openai' 4 | 5 | const configuration = new Configuration({ 6 | apiKey: process.env.OPENAI_API_KEY, 7 | }); 8 | 9 | const openai = new OpenAIApi(configuration); 10 | 11 | export default async function handler( 12 | req: NextApiRequest, 13 | res: NextApiResponse 14 | ) { 15 | const prompt = req.query.prompt; 16 | 17 | if (!prompt) { 18 | return res.status(400).json({ error: "Prompt missing" }); 19 | } 20 | 21 | if (prompt.length > 100) { 22 | return res.status(400).json({ error: "Prompt too long" }); 23 | } 24 | 25 | const completion = await openai.createCompletion({ 26 | model: "text-davinci-003", 27 | prompt: `Create a cringy motivational quote based on the following topic.\n 28 | Topic: ${prompt}\n 29 | Cringy motivational quote:`, 30 | max_tokens: 500, 31 | temperature: 1, 32 | presence_penalty: 0, 33 | frequency_penalty: 0, 34 | }); 35 | 36 | const quote = completion.data.choices[0].text; 37 | 38 | res.status(200).json({ quote }); 39 | } 40 | -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import Image from 'next/image' 3 | import styles from '@/styles/Home.module.css' 4 | import mainImage from '@/assets/images/main_image.jpg' 5 | import { Form, Button, Spinner } from 'react-bootstrap' 6 | import { FormEvent, useState } from 'react' 7 | 8 | export default function Home() { 9 | 10 | const [quote, setQuote] = useState(""); 11 | const [quoteLoading, setQuoteLoading] = useState(false); 12 | const [quoteLoadingError, setQuoteLoadingError] = useState(false); 13 | 14 | async function handleSubmit(e: FormEvent) { 15 | e.preventDefault(); 16 | const formData = new FormData(e.target as HTMLFormElement); 17 | const prompt = formData.get("prompt")?.toString().trim(); 18 | 19 | if (prompt) { 20 | try { 21 | setQuote(""); 22 | setQuoteLoadingError(false); 23 | setQuoteLoading(true); 24 | 25 | const response = await fetch("/api/cringe?prompt=" + encodeURIComponent(prompt)); 26 | const body = await response.json(); 27 | setQuote(body.quote); 28 | } catch (error) { 29 | console.error(error); 30 | setQuoteLoadingError(true); 31 | } finally { 32 | setQuoteLoading(false); 33 | } 34 | } 35 | } 36 | 37 | return ( 38 | <> 39 | 40 | Cringe AI - Create cringy motivational quotes 41 | 42 | 43 | 44 | 45 |
46 |

Cringe AI

47 |

powered by GPT-3

48 |
Enter a topic and the AI will generate a super cringy motivational quote
49 |
50 | A picture of a woman holding both her hands in front of her face 57 |
58 |
59 | 60 | Create a cringy quote about... 61 | 66 | 67 | 70 |
71 | {quoteLoading && } 72 | {quoteLoadingError && "Something went wrong. Please try again."} 73 | {quote &&
{quote}
} 74 |
75 | 76 | ) 77 | } 78 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codinginflow/nextjs-gpt3/b0dcfe026ebce79b9bf48054af46b83f55c8a282/public/favicon.ico -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/thirteen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .main { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | max-width: 600px; 6 | margin: auto; 7 | padding: 4rem 1rem; 8 | text-align: center; 9 | } 10 | 11 | .mainImageContainer { 12 | position: relative; 13 | margin: 2rem 0; 14 | width: 100%; 15 | aspect-ratio: 12/7; 16 | } 17 | 18 | .mainImage { 19 | object-fit: cover; 20 | border-radius: 40px; 21 | box-shadow: 0 3px 8px rgb(0 0 0 / 24%); 22 | } 23 | 24 | .inputForm { 25 | width: 100%; 26 | } -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | padding: 0; 4 | margin: 0; 5 | } 6 | 7 | html, 8 | body { 9 | max-width: 100vw; 10 | overflow-x: hidden; 11 | } 12 | 13 | body { 14 | background: #F5E5FC; 15 | } 16 | 17 | a { 18 | text-decoration: none; 19 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "baseUrl": ".", 18 | "paths": { 19 | "@/*": ["./*"] 20 | } 21 | }, 22 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 23 | "exclude": ["node_modules"] 24 | } 25 | --------------------------------------------------------------------------------