├── .eslintrc.json ├── src ├── app │ ├── favicon.ico │ ├── globals.css │ ├── layout.js │ ├── page.js │ ├── tokens │ │ └── page.js │ └── prompts │ │ ├── page.js │ │ └── [prompt_id] │ │ └── page.js └── lib │ ├── dbutil.js │ └── auth.js ├── jsconfig.json ├── next.config.js ├── postcss.config.js ├── .vscode └── settings.json ├── .env.example ├── .editorconfig ├── pages └── api │ ├── auth │ └── [...nextauth].js │ ├── tokens │ ├── generate │ │ └── index.js │ └── index.js │ ├── prompts │ ├── [prompt_id] │ │ └── index.js │ └── chat │ │ └── index.js │ ├── prompts.js │ └── parse.js ├── .gitignore ├── tailwind.config.js ├── public ├── vercel.svg └── next.svg ├── package.json └── README.md /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sugarforever/amazing-jo.js/main/src/app/favicon.ico -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@/*": ["./src/*"] 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {} 3 | 4 | module.exports = nextConfig 5 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[javascript]": { 3 | "editor.defaultFormatter": "vscode.typescript-language-features" 4 | } 5 | } -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | GITHUB_CLIENT_ID= 2 | GITHUB_CLIENT_SECRET= 3 | NEXTAUTH_SECRET= 4 | NEXTAUTH_URL= 5 | 6 | SUPABASE_URL= 7 | SUPABASE_API_KEY= -------------------------------------------------------------------------------- /src/lib/dbutil.js: -------------------------------------------------------------------------------- 1 | import { createClient } from '@supabase/supabase-js'; 2 | 3 | const getSupabaseClient = () => { 4 | return createClient(process.env.SUPABASE_URL, process.env.SUPABASE_API_KEY); 5 | } 6 | 7 | export { 8 | getSupabaseClient 9 | } 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 16 | 17 | [*.{vue,js}] 18 | indent_style = space 19 | indent_size = 2 20 | 21 | [docker-compose.yml] 22 | indent_size = 4 23 | -------------------------------------------------------------------------------- /pages/api/auth/[...nextauth].js: -------------------------------------------------------------------------------- 1 | import NextAuth from "next-auth" 2 | import GithubProvider from "next-auth/providers/github" 3 | export const authOptions = { 4 | // Configure one or more authentication providers 5 | providers: [ 6 | GithubProvider({ 7 | clientId: process.env.GITHUB_CLIENT_ID, 8 | clientSecret: process.env.GITHUB_CLIENT_SECRET, 9 | }), 10 | // ...add more providers here 11 | ], 12 | } 13 | export default NextAuth(authOptions) -------------------------------------------------------------------------------- /.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 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | './src/pages/**/*.{js,ts,jsx,tsx,mdx}', 5 | './src/components/**/*.{js,ts,jsx,tsx,mdx}', 6 | './src/app/**/*.{js,ts,jsx,tsx,mdx}', 7 | ], 8 | theme: { 9 | extend: { 10 | backgroundImage: { 11 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 12 | 'gradient-conic': 13 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 14 | }, 15 | }, 16 | }, 17 | plugins: [], 18 | } 19 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | 19 | body { 20 | color: rgb(var(--foreground-rgb)); 21 | background: linear-gradient( 22 | to bottom, 23 | transparent, 24 | rgb(var(--background-end-rgb)) 25 | ) 26 | rgb(var(--background-start-rgb)); 27 | } 28 | -------------------------------------------------------------------------------- /src/lib/auth.js: -------------------------------------------------------------------------------- 1 | import { getSupabaseClient } from '@/lib/dbutil'; 2 | 3 | const validate = async (token) => { 4 | if (!token) { 5 | return null; 6 | } 7 | 8 | const supabase = getSupabaseClient(); 9 | try { 10 | const { data, error } = await supabase 11 | .from('tokens') 12 | .select('*') 13 | .eq('token', token) 14 | .limit(1); 15 | 16 | if (error) { 17 | console.error('Error querying data:', error.message); 18 | return null; 19 | } 20 | return data.length > 0 ? data[0].user_name : null; 21 | } catch (error) { 22 | console.error('Error querying data:', error.message); 23 | return null; 24 | } 25 | } 26 | 27 | export { 28 | validate 29 | } 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "amazing-jo.js", 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 | "@emotion/react": "^11.11.1", 13 | "@emotion/styled": "^11.11.0", 14 | "@mui/icons-material": "^5.14.8", 15 | "@mui/lab": "5.0.0-alpha.143", 16 | "@mui/material": "^5.14.8", 17 | "@supabase/auth-helpers-nextjs": "^0.7.4", 18 | "@supabase/auth-helpers-react": "^0.4.2", 19 | "@supabase/supabase-js": "^2.33.2", 20 | "@vercel/analytics": "^1.0.2", 21 | "autoprefixer": "10.4.15", 22 | "eslint": "8.48.0", 23 | "eslint-config-next": "13.4.19", 24 | "express": "^4.18.2", 25 | "jsdom": "^22.1.0", 26 | "langchain": "^0.0.135", 27 | "next": "13.4.19", 28 | "next-auth": "^4.23.1", 29 | "openai": "^4.2.0", 30 | "postcss": "8.4.28", 31 | "react": "18.2.0", 32 | "react-markdown": "^8.0.7", 33 | "rehype-highlight": "^7.0.0", 34 | "remark": "^14.0.3", 35 | "remark-gfm": "^3.0.1", 36 | "remark-html": "^15.0.2", 37 | "tailwindcss": "3.3.3", 38 | "uuid": "^9.0.1", 39 | "zod": "^3.22.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pages/api/tokens/generate/index.js: -------------------------------------------------------------------------------- 1 | import { getSupabaseClient } from '@/lib/dbutil'; 2 | import { getServerSession } from "next-auth/next" 3 | import { authOptions } from "../../auth/[...nextauth]" 4 | import { v4 as uuidv4 } from "uuid"; 5 | 6 | export default async function handler(req, res) { 7 | res.setHeader('Cache-Control', 'no-cache'); 8 | 9 | const session = await getServerSession(req, res, authOptions) 10 | const email = session?.user?.email; 11 | 12 | if (req.method === 'POST') { 13 | if (email) { 14 | const supabase = getSupabaseClient(); 15 | try { 16 | const { data, error } = await supabase 17 | .from('tokens') 18 | .upsert( 19 | [ 20 | { 21 | user_name: email, 22 | token: uuidv4() 23 | }, 24 | ], 25 | { onConflict: ["user_name"] } 26 | ) 27 | .select(); 28 | 29 | if (error) { 30 | console.error('Error inserting data:', error.message); 31 | res.status(500) 32 | } else { 33 | res.status(200).json({ 34 | token: data[0] 35 | }); 36 | } 37 | } catch (error) { 38 | console.error('Error inserting data:', error.message); 39 | res.status(500) 40 | } 41 | } else { 42 | res.status(401).json({ error: 'Unauthorized' }); 43 | } 44 | } else { 45 | res.status(405).json({ error: 'Method not allowed.' }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /pages/api/tokens/index.js: -------------------------------------------------------------------------------- 1 | import { getSupabaseClient } from '@/lib/dbutil'; 2 | import { getServerSession } from "next-auth/next" 3 | import { authOptions } from "../auth/[...nextauth]" 4 | 5 | const queryTokens = async (email) => { 6 | const supabase = getSupabaseClient(); 7 | try { 8 | const { data, error } = await supabase 9 | .from('tokens') 10 | .select('*') 11 | .eq('user_name', email); 12 | 13 | return { data, error }; 14 | } catch (error) { 15 | console.error('Error querying data:', error.message); 16 | return { data: [], error } 17 | } 18 | } 19 | 20 | export default async function handler(req, res) { 21 | res.setHeader('Cache-Control', 'no-cache'); 22 | 23 | const session = await getServerSession(req, res, authOptions) 24 | const email = session?.user?.email; 25 | 26 | if (req.method === 'GET') { 27 | if (email) { 28 | const { data, error } = await queryTokens(email); 29 | if (error) { 30 | console.error('Error querying data:', error.message); 31 | res.status(500).json({ 32 | error: error.message 33 | }) 34 | } else { 35 | if (data) { 36 | res.status(200).json({ 37 | tokens: data 38 | }); 39 | } else { 40 | res.status(404); 41 | } 42 | } 43 | } else { 44 | res.status(401).json({ error: 'Unauthorized' }); 45 | } 46 | } else { 47 | res.status(405).json({ error: 'Method not allowed.' }); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | ``` 14 | 15 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 16 | 17 | You can start editing the page by modifying `app/page.js`. The page auto-updates as you edit the file. 18 | 19 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | 30 | ## Deploy on Vercel 31 | 32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /pages/api/prompts/[prompt_id]/index.js: -------------------------------------------------------------------------------- 1 | import { getSupabaseClient } from '@/lib/dbutil'; 2 | import { getServerSession } from "next-auth/next" 3 | import { authOptions } from "../../auth/[...nextauth]" 4 | 5 | const queryPrompt = async (email, prompt_id) => { 6 | const supabase = getSupabaseClient(); 7 | try { 8 | const { data, error } = await supabase 9 | .from('prompts') 10 | .select('*') 11 | .eq('id', prompt_id) 12 | .eq('user_name', email) 13 | .limit(1); 14 | 15 | return { data, error }; 16 | } catch (error) { 17 | console.error('Error querying data:', error.message); 18 | return { data: [], error } 19 | } 20 | } 21 | 22 | export default async function handler(req, res) { 23 | res.setHeader('Cache-Control', 'no-cache'); 24 | 25 | const session = await getServerSession(req, res, authOptions) 26 | const email = session?.user?.email; 27 | const { prompt_id } = req.query; 28 | 29 | if (req.method === 'GET') { 30 | if (email) { 31 | const { data, error } = await queryPrompt(email, prompt_id); 32 | if (error) { 33 | console.error('Error querying data:', error.message); 34 | res.status(500).json({ 35 | error: error.message 36 | }) 37 | } else { 38 | const [first] = data; 39 | if (first) { 40 | res.status(200).json({ 41 | prompt: first 42 | }); 43 | } else { 44 | res.status(404); 45 | } 46 | } 47 | } else { 48 | res.status(401).json({ error: 'Unauthorized' }); 49 | } 50 | } else { 51 | res.status(405).json({ error: 'Method not allowed.' }); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/app/layout.js: -------------------------------------------------------------------------------- 1 | 2 | import './globals.css'; 3 | import { Inter } from 'next/font/google'; 4 | import { getServerSession } from "next-auth/next" 5 | import { authOptions } from "pages/api/auth/[...nextauth]" 6 | import Link from 'next/link'; 7 | import { Analytics } from '@vercel/analytics/react'; 8 | 9 | const inter = Inter({ subsets: ['latin'] }) 10 | 11 | const pages = ['Prompts']; 12 | const settings = ['Logout']; 13 | 14 | export const metadata = { 15 | title: "Amazing JO", 16 | description: "Amazing JO is the AI driven services hub that provides useful features to customers.", 17 | } 18 | 19 | export default async function RootLayout({ children }) { 20 | const session = await getServerSession(authOptions) 21 | 22 | return ( 23 | 24 | 25 |
26 | 37 |
38 |
39 | {children} 40 | 41 |
42 | 43 | 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /src/app/page.js: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { TextField, Button } from '@mui/material'; 4 | import { useState } from 'react'; 5 | 6 | export default function Home() { 7 | 8 | const [openaiApiKey, setOpenaiApiKey] = useState(''); 9 | const [recipeUrl, setRecipeUrl] = useState(''); 10 | const [recipe, setRecipe] = useState(null); 11 | 12 | const handleSubmit = async (event) => { 13 | setRecipe(null); 14 | event.preventDefault(); 15 | 16 | const response = await fetch('/api/parse', { 17 | method: 'POST', 18 | headers: { 19 | 'Content-Type': 'application/json', 20 | }, 21 | body: JSON.stringify({ openaiApiKey, recipeUrl }), 22 | }); 23 | 24 | if (response.ok) { 25 | const data = await response.json(); 26 | setRecipe(data); 27 | } else { 28 | console.error('Error parsing recipe'); 29 | } 30 | }; 31 | 32 | return ( 33 |
34 |
35 |

🥑 Amazing JO‘s Recipe

36 |
37 | setOpenaiApiKey(e.target.value)} /> 38 | setRecipeUrl(e.target.value)} /> 39 | 42 | 43 | {recipe !== null && ( 44 |
{JSON.stringify(recipe, null, 2)}
45 | )} 46 |
47 |
48 | 49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /pages/api/prompts.js: -------------------------------------------------------------------------------- 1 | import { createClient } from '@supabase/supabase-js'; 2 | import { getServerSession } from "next-auth/next" 3 | import { authOptions } from "./auth/[...nextauth]" 4 | 5 | const getSupabaseClient = () => { 6 | return createClient(process.env.SUPABASE_URL, process.env.SUPABASE_API_KEY); 7 | } 8 | 9 | const queryPrompts = async (email) => { 10 | const supabase = getSupabaseClient(); 11 | try { 12 | const { data, error } = await supabase.from('prompts').select('*').eq('user_name', email); 13 | return { data, error }; 14 | } catch (error) { 15 | console.error('Error querying data:', error.message); 16 | return { data: [], error } 17 | } 18 | } 19 | 20 | export default async function handler(req, res) { 21 | res.setHeader('Cache-Control', 'no-cache'); 22 | 23 | const session = await getServerSession(req, res, authOptions) 24 | const email = session?.user?.email; 25 | 26 | if (req.method === 'GET') { 27 | if (email) { 28 | const { data, error } = await queryPrompts(email); 29 | if (error) { 30 | console.error('Error querying data:', error.message); 31 | res.status(500) 32 | } else { 33 | res.status(200).json({ 34 | prompts: data 35 | }); 36 | } 37 | } else { 38 | res.status(401).json({ error: 'Unauthorized' }); 39 | } 40 | } else if (req.method === 'POST') { 41 | const { prompt, name } = req.body; 42 | if (email) { 43 | const supabase = getSupabaseClient(); 44 | try { 45 | const { data, error } = await supabase.from('prompts').insert([ 46 | { prompt, name, user_name: email }, 47 | ]).select(); 48 | 49 | if (error) { 50 | console.error('Error inserting data:', error.message); 51 | res.status(500) 52 | } else { 53 | res.status(200).json({ 54 | prompt: data[0] 55 | }); 56 | } 57 | } catch (error) { 58 | console.error('Error inserting data:', error.message); 59 | res.status(500) 60 | } 61 | } else { 62 | res.status(401).json({ error: 'Unauthorized' }); 63 | } 64 | } else { 65 | res.status(405).json({ error: 'Method not allowed.' }); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/app/tokens/page.js: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { Button, Divider, Grid, Item, List, ListItemButton, ListItemText, TextField } from '@mui/material'; 4 | import LoadingButton from '@mui/lab/LoadingButton'; 5 | import { useEffect, useState, useMemo, Fragment } from 'react'; 6 | 7 | export default function Prompts({ session }) { 8 | const [token, setToken] = useState(''); 9 | const [loading, setLoading] = useState(false); 10 | 11 | useEffect(() => { 12 | async function fetchData() { 13 | const response = await fetch('/api/tokens', { 14 | method: 'GET', 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | } 18 | }); 19 | 20 | if (response.ok) { 21 | const { tokens } = await response.json(); 22 | const [first] = tokens; 23 | if (first) 24 | setToken(first.token); 25 | } else { 26 | console.error('Error querying tokens'); 27 | } 28 | } 29 | 30 | fetchData(); 31 | }, []); 32 | 33 | const handleSubmit = async (event) => { 34 | event.preventDefault(); 35 | setLoading(true); 36 | 37 | const response = await fetch('/api/tokens/generate', { 38 | method: 'POST', 39 | headers: { 40 | 'Content-Type': 'application/json', 41 | } 42 | }); 43 | 44 | if (response.ok) { 45 | const data = await response.json(); 46 | const { token } = data; 47 | setToken(token.token); 48 | } else { 49 | console.error('Error generating a new token'); 50 | } 51 | 52 | setLoading(false); 53 | }; 54 | 55 | return ( 56 |
57 |
58 |

🔑 API Tokens

59 |
60 | 65 | 66 | Generate 67 | 68 | 69 |
70 |
71 | ) 72 | } 73 | -------------------------------------------------------------------------------- /pages/api/parse.js: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { ChatOpenAI } from "langchain/chat_models/openai"; 3 | import { PromptTemplate } from "langchain/prompts"; 4 | import { StructuredOutputParser } from "langchain/output_parsers"; 5 | import { HumanMessage } from "langchain/schema"; 6 | import { JSDOM } from "jsdom"; 7 | 8 | const parser = StructuredOutputParser.fromZodSchema( 9 | z.object({ 10 | name: z.string().describe("The name of the recipe"), 11 | ingredients: z 12 | .array(z.object({ 13 | name: z.string().describe("The name of the ingredient"), 14 | quantity: z.string().describe("The specific unit of measurement corresponding to the quantity, such as grams, ounces, liters, etc."), 15 | unit: z.string().describe("The amount of the ingredient required for the recipe. This can be represented using various units such as grams, cups, teaspoons, etc."), 16 | })) 17 | .describe("The list of ingredients for the recipe."), 18 | }) 19 | ); 20 | 21 | const formatInstructions = parser.getFormatInstructions(); 22 | const prompt = new PromptTemplate({ 23 | template: "Extract the recipe ingredients from the following HTML markup:\n{html}.\n{format_instructions}\n", 24 | inputVariables: ["html"], 25 | partialVariables: { format_instructions: formatInstructions }, 26 | }); 27 | 28 | export default async function handler(req, res) { 29 | if (req.method === 'POST') { 30 | const openaiApiKey = req.body.openaiApiKey; 31 | const recipeUrl = req.body.recipeUrl; 32 | 33 | console.log(`recipeUrl: ${recipeUrl}`); 34 | 35 | const recipeResponse = await fetch(recipeUrl); 36 | const htmlString = await recipeResponse.text(); 37 | const { document } = (new JSDOM(htmlString)).window; 38 | 39 | const elementById = document.getElementById('recipe-single'); 40 | const html = elementById.innerHTML; 41 | 42 | const chat = new ChatOpenAI({ 43 | temprature: 0, 44 | openAIApiKey: openaiApiKey, 45 | modelName: "gpt-3.5-turbo-16k" 46 | }); 47 | 48 | const content = await prompt.format({ html: html }); 49 | const response = await chat.call([ new HumanMessage(content) ]); 50 | 51 | const answer = await parser.parse(response.content); 52 | res.status(200).json(answer); 53 | } else { 54 | res.status(405).json({ error: 'Method not allowed.' }); 55 | } 56 | } -------------------------------------------------------------------------------- /pages/api/prompts/chat/index.js: -------------------------------------------------------------------------------- 1 | import { getSupabaseClient } from '@/lib/dbutil'; 2 | import { getServerSession } from "next-auth/next" 3 | import { authOptions } from "../../auth/[...nextauth]" 4 | import { ChatOpenAI } from "langchain/chat_models/openai"; 5 | import { PromptTemplate } from "langchain/prompts"; 6 | import { HumanMessage } from "langchain/schema"; 7 | 8 | const queryByToken = async (token) => { 9 | const supabase = getSupabaseClient(); 10 | try { 11 | const { data, error } = await supabase 12 | .from('tokens') 13 | .select('*') 14 | .eq('token', token) 15 | .limit(1); 16 | 17 | if (error) { 18 | console.error('Error querying tokens:', error.message); 19 | return null; 20 | } 21 | return data.length > 0 ? data[0].user_name : null; 22 | } catch (error) { 23 | console.error('Error querying data:', error.message); 24 | return null; 25 | } 26 | } 27 | 28 | const queryPrompt = async (email, prompt_id) => { 29 | const supabase = getSupabaseClient(); 30 | try { 31 | const { data, error } = await supabase 32 | .from('prompts') 33 | .select('*') 34 | .eq('id', prompt_id) 35 | .eq('user_name', email) 36 | .limit(1); 37 | 38 | if (error) { 39 | console.error('Error querying prompts:', error.message); 40 | return null; 41 | } 42 | return data.length > 0 ? data[0].prompt : null; 43 | } catch (error) { 44 | console.error('Error querying data:', error.message); 45 | return null; 46 | } 47 | } 48 | 49 | export default async function handler(req, res) { 50 | res.setHeader('Cache-Control', 'no-cache'); 51 | 52 | const token = req.headers['x-auth']; 53 | const openaiApiKey = req.headers['openai-api-key']; 54 | const prompt_id = req.body.prompt; 55 | const inputVars = req.body.inputs; 56 | 57 | if (req.method !== 'POST') { 58 | res.status(405).json({ error: 'Method not allowed.' }); 59 | return; 60 | } 61 | 62 | const userName = await queryByToken(token); 63 | if (!userName) { 64 | res.status(401).json({ error: 'Unauthorized' }); 65 | return; 66 | } 67 | const prompt = await queryPrompt(userName, prompt_id); 68 | 69 | if (!prompt) { 70 | res.status(404).json({ error: 'Prompt not found.' }); 71 | return; 72 | } 73 | 74 | const chat = new ChatOpenAI({ 75 | temprature: 0, 76 | openAIApiKey: openaiApiKey, 77 | modelName: "gpt-3.5-turbo-16k" 78 | }); 79 | 80 | const template = new PromptTemplate({ 81 | template: prompt, 82 | inputVariables: Object.keys(inputVars) 83 | }); 84 | 85 | const content = await template.format(inputVars); 86 | const response = await chat.call([new HumanMessage(content)]); 87 | res.status(200).json({ 88 | content: response?.content 89 | }); 90 | } 91 | -------------------------------------------------------------------------------- /src/app/prompts/page.js: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { Button, Divider, Grid, Item, List, ListItemButton, ListItemText, TextField } from '@mui/material'; 4 | import { useEffect, useState, useMemo, Fragment } from 'react'; 5 | 6 | export default function Prompts({ session }) { 7 | const [prompts, setPrompts] = useState([]); 8 | const [prompt, setPrompt] = useState(''); 9 | const [name, setName] = useState(''); 10 | 11 | useEffect(() => { 12 | async function fetchData() { 13 | const response = await fetch('/api/prompts', { 14 | method: 'GET', 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | } 18 | }); 19 | 20 | if (response.ok) { 21 | const { prompts } = await response.json(); 22 | console.log(prompts); 23 | setPrompts(prompts); 24 | } else { 25 | console.error('Error querying prompts'); 26 | } 27 | } 28 | 29 | fetchData(); 30 | }, []); 31 | 32 | const handleSubmit = async (event) => { 33 | event.preventDefault(); 34 | 35 | const response = await fetch('/api/prompts', { 36 | method: 'POST', 37 | headers: { 38 | 'Content-Type': 'application/json', 39 | }, 40 | body: JSON.stringify({ prompt, name }), 41 | }); 42 | 43 | if (response.ok) { 44 | const data = await response.json(); 45 | const { prompt } = data; 46 | setPrompts([...prompts, prompt]); 47 | } else { 48 | console.error('Error creating a new prompt'); 49 | } 50 | 51 | setPrompt(''); 52 | setName(''); 53 | }; 54 | 55 | return ( 56 |
57 |
58 |

🥑 Amazing Prompts

59 |
60 | setName(e.target.value)} /> 66 | setPrompt(e.target.value)} /> 74 | 77 | 78 |
79 | 80 | {prompts.map((prompt, index) => ( 81 | 82 | 83 | 87 | 88 | {index < prompts.length - 1 && ( 89 | 90 | )} 91 | 92 | ))} 93 | 94 |
95 |
96 |
97 | ) 98 | } 99 | -------------------------------------------------------------------------------- /src/app/prompts/[prompt_id]/page.js: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { TextField, Typography } from '@mui/material'; 4 | import LoadingButton from '@mui/lab/LoadingButton'; 5 | import { useEffect, useState, useMemo, Fragment } from 'react'; 6 | import { ChatOpenAI } from "langchain/chat_models/openai"; 7 | import { PromptTemplate } from "langchain/prompts"; 8 | import { HumanMessage } from "langchain/schema"; 9 | import ReactMarkdown from 'react-markdown'; 10 | import rehypeHighlight from 'rehype-highlight'; 11 | 12 | const extractVariables = (template) => { 13 | const regex = /{{\s*([\w]+)\s*}}|{\s*([\w]+)\s*}/g; 14 | const matches = []; 15 | let match; 16 | 17 | while ((match = regex.exec(template))) { 18 | if (match[1]) { 19 | // Double brackets indicate no variable 20 | continue; 21 | } 22 | matches.push(match[2]); 23 | } 24 | 25 | return matches; 26 | } 27 | 28 | export default function Prompt({ session, params }) { 29 | const { prompt_id } = params; 30 | const [prompt, setPrompt] = useState(null); 31 | const [openaiApiKey, setOpenaiApiKey] = useState(''); 32 | const [inputVars, setInputVars] = useState({}); 33 | const [answer, setAnswer] = useState(null); 34 | const [loading, setLoading] = useState(false); 35 | 36 | const promptInputVars = useMemo(() => { 37 | const vars = prompt ? extractVariables(prompt.prompt) : []; 38 | const initialInputValues = {}; 39 | vars.forEach((input) => { 40 | initialInputValues[input] = ''; 41 | }); 42 | setInputVars(initialInputValues); 43 | return vars; 44 | }, [prompt]); 45 | 46 | const handleInputChange = (e, inputName) => { 47 | const newValue = e.target.value; 48 | setInputVars((prevInputValues) => ({ 49 | ...prevInputValues, 50 | [inputName]: newValue, 51 | })); 52 | }; 53 | 54 | const ask = async (event) => { 55 | event.preventDefault(); 56 | setAnswer(null); 57 | setLoading(true); 58 | 59 | const chat = new ChatOpenAI({ 60 | temprature: 0, 61 | openAIApiKey: openaiApiKey, 62 | modelName: "gpt-3.5-turbo-16k" 63 | }); 64 | 65 | const template = new PromptTemplate({ 66 | template: prompt.prompt, 67 | inputVariables: Object.keys(inputVars) 68 | }); 69 | 70 | const content = await template.format(inputVars); 71 | const response = await chat.call([new HumanMessage(content)]); 72 | setLoading(false); 73 | setAnswer(response?.content); 74 | }; 75 | 76 | useEffect(() => { 77 | async function fetchData() { 78 | const response = await fetch(`/api/prompts/${prompt_id}`, { 79 | method: 'GET', 80 | headers: { 81 | 'Content-Type': 'application/json', 82 | } 83 | }); 84 | 85 | if (response.ok) { 86 | const data = await response.json(); 87 | const { prompt } = data; 88 | setPrompt(prompt); 89 | } else { 90 | console.error('Error creating a new prompt'); 91 | } 92 | } 93 | 94 | fetchData(); 95 | }, [prompt_id]); 96 | 97 | return ( 98 |
99 |
100 |

🥑 {prompt?.name || 'Amazing Prompt'}

101 | 102 | {prompt?.prompt} 103 | 104 |
105 | setOpenaiApiKey(e.target.value)} 112 | /> 113 | {promptInputVars.map((inputVar, index) => ( 114 | handleInputChange(e, inputVar)} 121 | /> 122 | ))} 123 | 124 | Ask 125 | 126 | 127 | 128 |
129 | 130 | {answer} 131 | 132 |
133 |
134 |
135 | ) 136 | } 137 | --------------------------------------------------------------------------------