├── .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 | 
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 |
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 |
57 |
58 |
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 |
--------------------------------------------------------------------------------