├── .nvmrc ├── .eslintrc.json ├── src └── app │ ├── favicon.ico │ ├── Highlighter.css │ ├── atoms.tsx │ ├── layout.tsx │ ├── globals.css │ ├── api │ └── route.ts │ ├── RegexVisualizer.tsx │ ├── RegexHighlighter.tsx │ ├── Message.tsx │ └── page.tsx ├── next.config.js ├── postcss.config.js ├── .vscode └── settings.json ├── .gitignore ├── tailwind.config.ts ├── public ├── vercel.svg └── next.svg ├── tsconfig.json ├── package.json ├── README.md └── pnpm-lock.yaml /.nvmrc: -------------------------------------------------------------------------------- 1 | v17.9.1 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sweepai/prompt-lab/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /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 | "editor.formatOnSave": true, 3 | "editor.defaultFormatter": "esbenp.prettier-vscode" 4 | } -------------------------------------------------------------------------------- /src/app/Highlighter.css: -------------------------------------------------------------------------------- 1 | .group1 { 2 | background-color: brown; 3 | } 4 | 5 | .group2 { 6 | background-color: darkgreen; 7 | } 8 | 9 | .group3 { 10 | background-color: darkblue; 11 | } -------------------------------------------------------------------------------- /src/app/atoms.tsx: -------------------------------------------------------------------------------- 1 | import { atom } from "recoil"; 2 | import { MessageType } from "./Message"; 3 | 4 | const messagesState = atom({ 5 | key: 'todoListState', 6 | default: [ 7 | { 8 | content: "You are a helpful assistant.", 9 | role: "system", 10 | id: 0 11 | }, 12 | { 13 | content: "Say this is a test.", 14 | role: "user", 15 | id: 1 16 | }, 17 | ], 18 | }); 19 | 20 | 21 | export { messagesState }; -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | import { Inter } from 'next/font/google' 3 | import './globals.css' 4 | 5 | const inter = Inter({ subsets: ['latin'] }) 6 | 7 | export const metadata: Metadata = { 8 | title: 'Prompt + Regex Lab', 9 | description: 'Made by Sweep AI', 10 | } 11 | 12 | export default function RootLayout({ 13 | children, 14 | }: { 15 | children: React.ReactNode 16 | }) { 17 | return ( 18 | 19 | {children} 20 | 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /.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 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.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 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'tailwindcss' 2 | 3 | const config: Config = { 4 | content: [ 5 | './src/pages/**/*.{js,ts,jsx,tsx,mdx}', 6 | './src/components/**/*.{js,ts,jsx,tsx,mdx}', 7 | './src/app/**/*.{js,ts,jsx,tsx,mdx}', 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 13 | 'gradient-conic': 14 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 15 | }, 16 | }, 17 | }, 18 | plugins: [], 19 | } 20 | export default config 21 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "es5", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "bundler", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "paths": { 23 | "@/*": ["./src/*"] 24 | } 25 | }, 26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 27 | "exclude": ["node_modules"] 28 | } 29 | -------------------------------------------------------------------------------- /src/app/api/route.ts: -------------------------------------------------------------------------------- 1 | import OpenAI from 'openai' 2 | import { OpenAIStream, StreamingTextResponse } from 'ai' 3 | import { NextResponse } from 'node_modules/next/server' 4 | 5 | export const dynamic = 'force-dynamic' // defaults to force-static 6 | 7 | export async function GET(request: Request) { 8 | return new Response('Playground is up and running!') 9 | } 10 | 11 | export const runtime = 'edge' 12 | 13 | export async function POST(req: Request) { 14 | const { messages, apiKey, stream=true } = await req.json() 15 | const openai = new OpenAI({apiKey}) 16 | const response = await openai.chat.completions.create({ 17 | model: 'gpt-4', 18 | stream: stream, 19 | messages, 20 | }) 21 | 22 | if (stream) { 23 | // @ts-ignore 24 | const stream = OpenAIStream(response) 25 | return new StreamingTextResponse(stream) 26 | } else { 27 | return NextResponse.json(response) 28 | } 29 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prompt-lab", 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 | "ace-builds": "^1.31.2", 13 | "ai": "^2.2.25", 14 | "class-variance-authority": "^0.7.0", 15 | "clsx": "^2.0.0", 16 | "highlight.js": "^11.9.0", 17 | "lucide-react": "^0.292.0", 18 | "mark.js": "^8.11.1", 19 | "next": "14.0.3", 20 | "openai": "^4.19.1", 21 | "react": "^18", 22 | "react-ace": "^10.1.0", 23 | "react-dom": "^18", 24 | "react-string-replace": "^1.1.1", 25 | "react-syntax-highlighter": "^15.5.0", 26 | "react-toastify": "^9.1.3", 27 | "recoil": "^0.7.7", 28 | "tailwind-merge": "^2.0.0", 29 | "tailwindcss-animate": "^1.0.7" 30 | }, 31 | "devDependencies": { 32 | "@types/node": "^20", 33 | "@types/react": "^18", 34 | "@types/react-dom": "^18", 35 | "autoprefixer": "^10.0.1", 36 | "eslint": "^8", 37 | "eslint-config-next": "14.0.3", 38 | "postcss": "^8", 39 | "tailwindcss": "^3.3.0", 40 | "typescript": "^5" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/RegexVisualizer.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useRef, useState } from "react" 4 | import { ToastContainer, toast } from 'react-toastify'; 5 | import RegexHighlighter from "./RegexHighlighter" 6 | 7 | import 'react-toastify/dist/ReactToastify.css'; 8 | 9 | const RegexVisualizer = ({text, runButton}: {text: string, runButton: JSX.Element}) => { 10 | const defaultRegex = "(.*)" 11 | const [regex, setRegex] = useState(new RegExp(defaultRegex, "dgs")) 12 | const ref = useRef(null) 13 | const onChange = () => { 14 | if (ref.current) { 15 | try { 16 | setRegex(new RegExp(ref.current.value, "dgs")) 17 | } catch (e: any) { 18 | toast.error(e.message) 19 | } 20 | } 21 | } 22 | return ( 23 |
24 |
25 | 26 | {runButton} 27 |
28 | 29 | 32 |
33 | ) 34 | } 35 | 36 | export default RegexVisualizer -------------------------------------------------------------------------------- /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 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | 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. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 37 | -------------------------------------------------------------------------------- /src/app/RegexHighlighter.tsx: -------------------------------------------------------------------------------- 1 | type RegexHighlighterProps = { 2 | text: string; 3 | regex: RegExp; 4 | }; 5 | 6 | function randomHexColor(seed: number) { 7 | return [ 8 | "darkgreen", "darkblue", "darkred", "brown" 9 | ][seed % 5] 10 | } 11 | 12 | const replaceNewlines = (text: string) => { 13 | // return text.replace(/\n/g, " ") 14 | return text 15 | } 16 | 17 | const RegexHighlighter: React.FC = ({ text, regex }) => { 18 | const getHighlightedText = () => { 19 | if (regex.source === "") { 20 | return text; 21 | } 22 | 23 | const result: JSX.Element[] = []; 24 | let lastIndex = 0; 25 | 26 | var regexToUse = new RegExp(regex.source, "dgs"); 27 | 28 | const copiedRegex = new RegExp(regex.source, "dgs"); 29 | var search = text.search(copiedRegex); 30 | 31 | if (search === -1) { 32 | for (let i = regex.source.length; i > 2; i--) { 33 | try { 34 | regexToUse = new RegExp(regex.source.substring(0, i), "dgs"); 35 | } catch (e) { 36 | continue; 37 | } 38 | search = text.search(regexToUse); 39 | if (search !== -1) { 40 | break; 41 | } 42 | } 43 | if (search === -1) { 44 | return text 45 | } 46 | } 47 | 48 | text.replace(regexToUse, (match, ...args) => { 49 | const currentResults: JSX.Element[] = []; 50 | const matchStart = args[args.length - 2]; 51 | const matchEnd = matchStart + match.length; 52 | 53 | result.push({replaceNewlines(text.substring(lastIndex, matchStart))}); 54 | 55 | let currentIndex = matchStart; 56 | 57 | const colorChanges: [number, boolean][] = []; 58 | 59 | var firstEnd = matchEnd + 1; 60 | 61 | const copied_regex = new RegExp(regexToUse.source, "dgs") 62 | const exec_match = copied_regex.exec(match) 63 | 64 | if (exec_match == null || exec_match!.indices === undefined) { 65 | console.warn("exec_match is null") 66 | lastIndex = matchEnd; 67 | return match; 68 | } 69 | 70 | // @ts-ignore 71 | exec_match.indices.forEach(([start, end]: [number, number]) => { 72 | colorChanges.push([start + matchStart, true]); 73 | colorChanges.push([end + matchStart, false]); 74 | if (start > 0) { 75 | firstEnd = Math.min(firstEnd, start + matchStart); 76 | } 77 | }) 78 | 79 | colorChanges.sort((a, b) => a[0] - b[0]); 80 | 81 | var depth = 0; 82 | for (let i = 0; i < colorChanges.length - 1; i++) { 83 | const [index, isStart] = colorChanges[i]; 84 | depth += isStart ? 1 : -1; 85 | currentResults.push( 86 | 87 | {replaceNewlines(text.substring(index, colorChanges[i + 1][0]))} 88 | 89 | ); 90 | } 91 | 92 | lastIndex = matchEnd; 93 | currentResults.forEach((item) => { 94 | result.push(item); 95 | }); 96 | return match; 97 | }); 98 | 99 | if (lastIndex < text.length) { 100 | result.push({replaceNewlines(text.substring(lastIndex))}); 101 | } 102 | 103 | return result; 104 | }; 105 | 106 | const highlightedText = getHighlightedText() 107 | 108 | return ( 109 |
110 |       {highlightedText}
111 |     
112 | ); 113 | }; 114 | 115 | export default RegexHighlighter; -------------------------------------------------------------------------------- /src/app/Message.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useEffect, useRef } from "react"; 4 | import AceEditor from "react-ace"; 5 | 6 | import 'ace-builds/src-noconflict/theme-monokai'; // Import the theme 7 | import 'ace-builds/src-noconflict/mode-xml'; 8 | 9 | 10 | 11 | type RoleType = "system" | "user" | "assistant"; 12 | 13 | const renderRole = (role: RoleType) => { 14 | switch (role) { 15 | case "system": 16 | return "System"; 17 | case "user": 18 | return "User"; 19 | case "assistant": 20 | return "Assistant"; 21 | } 22 | }; 23 | 24 | const getNextRole = (role: RoleType) => { 25 | switch (role) { 26 | case "system": 27 | return "user"; 28 | case "user": 29 | return "assistant"; 30 | case "assistant": 31 | return "system"; 32 | } 33 | }; 34 | 35 | const colorMapping: { [key in RoleType]: string } = { 36 | "system": "bg-gray-800 hover:bg-gray-900", 37 | "user": "bg-blue-800 hover:bg-blue-900", 38 | "assistant": "bg-green-800 hover:bg-green-900", 39 | } 40 | 41 | const AutoScaledTextarea = ({ ...props }: React.TextareaHTMLAttributes) => { 42 | const ref = useRef(null); 43 | useEffect(() => { 44 | if (ref.current) { 45 | ref.current.style.height = 'auto'; 46 | ref.current.style.height = ref.current.scrollHeight + 'px'; 47 | } 48 | }, []) 49 | return ( 50 |