diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..096b7c4 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +OPENAI_API_KEY= diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..59b2887 --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +package-lock.json + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# env files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local diff --git a/README.md b/README.md new file mode 100644 index 0000000..21693c7 --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +# OpenAI API - Next.js Starter Project + +This is a Javascript starter project for using the Open AI API to build a Node app. + +In this example app, you enter the name of a product and it outputs a product review, formatted in a very specific way in markdown. You can use this as a starting point to build purpose-built generative API functionality. + +Technologies used: + +- [OpenAI API](https://openai.com/api/) +- [Node.js](https://nodejs.org/en/) +- [Next.js](https://nextjs.org/) +- [React](https://reactjs.org/) +- [TailwindCSS](https://tailwindcss.com/) +- [Highlight.js](https://highlightjs.org/) + +## Setup + +1. If you don’t have Node.js installed, [install it from here](https://nodejs.org/en/) + +2. Clone this repository + +3. Navigate into the project directory + + ```bash + $ cd openai-api-next-starter + ``` + +4. Install the requirements + + ```bash + $ npm install + ``` + +5. Make a copy of the example environment variables file + + ```bash + $ cp .env.example .env + ``` + +6. Add your [API key](https://beta.openai.com/account/api-keys) to the newly created `.env` file + +7. Run the app + + ```bash + $ npm run dev + +You should now be able to access the app at [http://localhost:3000](http://localhost:3000). Happy hacking. + +## Deployment + +I've run this successfully on Vercel and I'd recommend them for any Next.js (they invented Next and their DX is the best!) + +Included in the repo is a vercel.json file. You need this to set a higher serverless function timeout since the Open AI PI takes about 20=30 seconds to compute. Please note that Hobby accounts with always timeout at 10s so you need to upgrade to a pro account for this to work. Totally worth it. \ No newline at end of file diff --git a/next-env.d.ts b/next-env.d.ts new file mode 100644 index 0000000..4f11a03 --- /dev/null +++ b/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/package.json b/package.json new file mode 100644 index 0000000..31042cb --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "openai-gpt3-node-starter", + "version": "1.0.0", + "author": "Gannon Hall", + "license": "MIT", + "private": false, + "description": "OpenAI GPT-3 Node Starter Project", + + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start" + }, + "dependencies": { + "highlight.js": "^11.7.0", + "next": "^12.1.6", + "openai": "^3.0.0", + "react": "17.0.2", + "react-dom": "17.0.2" + }, + "devDependencies": { + "@types/node": "^18.11.18", + "@types/react": "^18.0.26", + "autoprefixer": "^10.4.13", + "postcss": "^8.4.20", + "tailwindcss": "^3.2.4", + "typescript": "^4.9.4" + } +} diff --git a/pages/_app.js b/pages/_app.js new file mode 100644 index 0000000..2dacd4e --- /dev/null +++ b/pages/_app.js @@ -0,0 +1,6 @@ +// pages/_app.js +import './styles/globals.css' + +export default function MyApp({ Component, pageProps }) { + return +} \ No newline at end of file diff --git a/pages/api/generate.js b/pages/api/generate.js new file mode 100644 index 0000000..d6ffef5 --- /dev/null +++ b/pages/api/generate.js @@ -0,0 +1,64 @@ +import { Configuration, OpenAIApi } from "openai"; + +const configuration = new Configuration({ + apiKey: process.env.OPENAI_API_KEY, +}); +const openai = new OpenAIApi(configuration); + +export default async function (req, res) { + const completion = await openai.createCompletion({ + model: "text-davinci-003", + prompt: generatePrompt(req.body.animal), + max_tokens: 2000, + temperature: 0.6, + }); + res.status(200).json({ result: completion.data.choices[0].text }); +} + +function generatePrompt(animal) { + const capitalizedAnimal = animal; + return `Product name: KRK ROKIT 5 G4 5 inch Powered Studio Monitors + Review: + ## KRK ROKIT 5 G4 5 inch Powered Studio Monitors + + + + | Attribute | Score | + | --------- | ------ | + | Build quality | 8.0 | + | Sound quality | 8.5 | + | Playability / Ease-of-use | 8.5 | + | Compatibility | 8.0 | + | Value | 8.5 | + + The KRK ROKIT 5 G4 5 inch Powered Studio Monitors are a reliable choice for recording and mixing in a home studio or professional setting. With their bi-amped design and Kevlar drivers, these monitors deliver accurate sound with a wide frequency response. + + The ROKIT 5 G4 monitors feature a built-in DSP-driven EQ that allows for precise tailoring of the monitors' response to the room. The included Auto Room Correction feature uses a measurement microphone to optimize the monitors' response to the acoustic environment. + + + + ### Pros + - Accurate sound with wide frequency response + - Built-in DSP-driven EQ and Auto Room Correction feature + - Variety of connectivity options + + ### Cons + - Some users may find the bass response to be lacking + + ## Best For + The KRK ROKIT 5 G4 5 inch Powered Studio Monitors are suitable for recording and mixing in a home or professional studio. They are particularly useful for those who need precise control over the monitors' response to the room. + + ## Key Specifications + - Frequency response: 43Hz-40kHz + - Power: 50 W RMS / 100 W peak + - Inputs: XLR, TRS, RCA + - Outputs: 1/8" stereo headphone output + + + + Product name: ${capitalizedAnimal} + Review:`; +} + + + diff --git a/pages/index.js b/pages/index.js new file mode 100644 index 0000000..d89a65f --- /dev/null +++ b/pages/index.js @@ -0,0 +1,152 @@ +import Head from "next/head"; +import { useState, useRef, useEffect } from "react"; +import hljs from "highlight.js"; + +export default function Home() { + // Create a ref for the div element + const textDivRef = useRef(null); + + const [animalInput, setAnimalInput] = useState(""); + const [result, setResult] = useState(); + const [isLoading, setIsLoading] = useState(false); + + // Add a click event listener to the copy icon that copies the text in the div to the clipboard when clicked + useEffect(() => { + const copyIcon = document.querySelector(".copy-icon"); + if (!copyIcon) return; + copyIcon.addEventListener("click", () => { + const textDiv = textDivRef.current; + const text = textDiv.textContent; + + // Create a hidden textarea element + const textArea = document.createElement("textarea"); + textArea.value = text; + document.body.appendChild(textArea); + + // Select the text in the textarea + textArea.select(); + + // Copy the text to the clipboard + document.execCommand("copy"); + + // Remove the textarea element + document.body.removeChild(textArea); + }); + }, []); // Run this only once + + async function onSubmit(event) { + event.preventDefault(); + setIsLoading(true); + const response = await fetch("/api/generate", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ animal: animalInput }), + }); + const data = await response.json(); + console.log("data", data); + console.log("data.result", data.result); + + let rawResult = data.result; + + console.log("rawResult"); + + const hljsResult = hljs.highlightAuto(rawResult).value; + + setResult(hljsResult); + setAnimalInput(""); + setIsLoading(false); + } + + return ( +
+ + training exp + + +
+

+ Product Review Generator +

+

+ Open AI starter app to generate product reviews +

+
+ setAnimalInput(e.target.value)} + /> + + +
+ {isLoading ? ( +

Loading... Be patient.. may take 30s+

+ ) : result ? ( +
+
+
+                
+              
+
+ + + + + + + +
+ ) : null} +
+
+ ); +} + +{ + /* +
+ +
+      
+    
+ +
{result}
+ +
{result}
+ +*/ +} diff --git a/pages/styles/globals.css b/pages/styles/globals.css new file mode 100644 index 0000000..7a75065 --- /dev/null +++ b/pages/styles/globals.css @@ -0,0 +1,137 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +/*! + Theme: Default + Description: Original highlight.js style + Author: (c) Ivan Sagalaev + Maintainer: @highlightjs/core-team + Website: https://highlightjs.org/ + License: see project LICENSE + Touched: 2021 +*/ + +/* +This is left on purpose making default.css the single file that can be lifted +as-is from the repository directly without the need for a build step + +Typically this "required" baseline CSS is added by `makestuff.js` during build. +*/ +pre code.hljs { + display: block; + overflow-x: auto; + padding: 1em; + } + + code.hljs { + padding: 3px 5px; + } + /* end baseline CSS */ + + .hljs { + background: #F3F3F3; + color: #444; + } + + /* Base color: saturation 0; */ + + .hljs-subst { + /* default */ + } + + /* purposely ignored */ + .hljs-formula, + .hljs-attr, + .hljs-property, + .hljs-params {} + + .hljs-comment { + color: #697070; + } + .hljs-tag, + .hljs-punctuation { + color: #444a; + } + + .hljs-tag .hljs-name, + .hljs-tag .hljs-attr { + color: #444; + } + + + .hljs-keyword, + .hljs-attribute, + .hljs-selector-tag, + .hljs-meta .hljs-keyword, + + .hljs-doctag, + .hljs-name { + font-weight: bold; + } + + + /* User color: hue: 0 */ + + .hljs-type, + .hljs-string, + .hljs-number, + .hljs-selector-id, + .hljs-selector-class, + .hljs-quote, + .hljs-template-tag, + .hljs-deletion { + color: #880000; + } + + .hljs-title, + .hljs-section { + color: #880000; + font-weight: bold; + } + + .hljs-regexp, + .hljs-symbol, + .hljs-variable, + .hljs-template-variable, + .hljs-link, + .hljs-selector-attr, + .hljs-operator, + .hljs-selector-pseudo { + color: #ab5656; + } + + /* Language color: hue: 90; */ + + .hljs-literal { + color: #695; + } + + .hljs-built_in, + .hljs-bullet, + .hljs-code, + .hljs-addition { + color: #397300; + } + + + /* Meta color: hue: 200 */ + + .hljs-meta { + color: #1f7199; + } + + .hljs-meta .hljs-string { + color: #38a; + } + + + /* Misc effects */ + + .hljs-emphasis { + font-style: italic; + } + + .hljs-strong { + font-weight: bold; + } \ No newline at end of file diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..33ad091 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..e4c7ae4 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,13 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + "./pages/**/*.{js,ts,jsx,tsx}", + "./components/**/*.{js,ts,jsx,tsx}", + ], + theme: { + container: { + center: true, + }, + }, + plugins: [], +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..aa20415 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "incremental": true, + "esModuleInterop": true, + "module": "esnext", + "resolveJsonModule": true, + "moduleResolution": "node", + "isolatedModules": true, + "jsx": "preserve" + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..e0fb3e9 --- /dev/null +++ b/vercel.json @@ -0,0 +1,7 @@ +{ + "functions": { + "pages/api/generate.js": { + "maxDuration": 60 + } + } + } \ No newline at end of file