-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
20 changed files
with
3,172 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# Created by Vercel CLI | ||
OPENAI_API_KEY= | ||
# The temperature controls how much randomness is in the output | ||
AI_TEMP=0.7 | ||
# The size of the response | ||
AI_MAX_TOKENS=100 | ||
OPENAI_API_ORG= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"root": true, | ||
"extends": "next/core-web-vitals" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# 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 | ||
dist | ||
|
||
# Misc | ||
.DS_Store | ||
*.pem | ||
|
||
# Debug | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
|
||
# Local ENV files | ||
.env.local | ||
.env.development.local | ||
.env.test.local | ||
.env.production.local | ||
|
||
# Vercel | ||
.vercel | ||
|
||
# Turborepo | ||
.turbo | ||
|
||
# typescript | ||
*.tsbuildinfo | ||
|
||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# AI Chat GPT-3 example | ||
|
||
This example shows how to implement a simple chat bot using Next.js, API Routes, and [OpenAI ChatGPT API](https://beta.openai.com/docs/api-reference/completions/create). | ||
|
||
### Components | ||
|
||
- Next.js | ||
- OpenAI API (ChatGPT) - streaming | ||
- API Routes (Edge runtime) - streaming | ||
|
||
## How to Use | ||
|
||
You can choose from one of the following two methods to use this repository: | ||
|
||
### One-Click Deploy | ||
|
||
Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=vercel-examples): | ||
|
||
[](https://vercel.com/new/clone?repository-url=https://github.com/vercel/examples/tree/main/solutions/ai-chatgpt&project-name=ai-chatgpt&repository-name=ai-chatgpt&env=OPENAI_API_KEY) | ||
|
||
### Clone and Deploy | ||
|
||
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [pnpm](https://pnpm.io/installation) to bootstrap the example: | ||
|
||
```bash | ||
pnpm create next-app --example https://github.com/vercel/examples/tree/main/solutions/ai-chatgpt | ||
``` | ||
|
||
#### Set up environment variables | ||
|
||
Rename [`.env.example`](.env.example) to `.env.local`: | ||
|
||
```bash | ||
cp .env.example .env.local | ||
``` | ||
|
||
then, update `OPENAI_API_KEY` with your [OpenAI](https://beta.openai.com/account/api-keys) secret key. | ||
|
||
Next, run Next.js in development mode: | ||
|
||
```bash | ||
pnpm dev | ||
``` | ||
|
||
The app should be up and running at http://localhost:3000. | ||
|
||
Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=edge-middleware-eap) ([Documentation](https://nextjs.org/docs/deployment)). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import clsx from 'clsx' | ||
|
||
export function Button({ className, ...props }: any) { | ||
return ( | ||
<button | ||
className={clsx( | ||
'inline-flex items-center gap-2 justify-center rounded-md py-2 px-3 text-sm outline-offset-2 transition active:transition-none', | ||
'bg-zinc-600 font-semibold text-zinc-100 hover:bg-zinc-400 active:bg-zinc-800 active:text-zinc-100/70', | ||
className | ||
)} | ||
{...props} | ||
/> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
import { useEffect, useState } from 'react' | ||
import { Button } from './Button' | ||
import { type ChatGPTMessage, ChatLine, LoadingChatLine } from './ChatLine' | ||
import { useCookies } from 'react-cookie' | ||
|
||
const COOKIE_NAME = 'nextjs-example-ai-chat-gpt3' | ||
|
||
// default first message to display in UI (not necessary to define the prompt) | ||
export const initialMessages: ChatGPTMessage[] = [ | ||
{ | ||
role: 'assistant', | ||
content: 'Hi! I am a friendly AI assistant. Ask me anything!', | ||
}, | ||
] | ||
|
||
const InputMessage = ({ input, setInput, sendMessage }: any) => ( | ||
<div className="mt-6 flex clear-both"> | ||
<input | ||
type="text" | ||
aria-label="chat input" | ||
required | ||
className="min-w-0 flex-auto appearance-none rounded-md border border-zinc-900/10 bg-white px-3 py-[calc(theme(spacing.2)-1px)] shadow-md shadow-zinc-800/5 placeholder:text-zinc-400 focus:border-teal-500 focus:outline-none focus:ring-4 focus:ring-teal-500/10 sm:text-sm" | ||
value={input} | ||
onKeyDown={(e) => { | ||
if (e.key === 'Enter') { | ||
sendMessage(input) | ||
setInput('') | ||
} | ||
}} | ||
onChange={(e) => { | ||
setInput(e.target.value) | ||
}} | ||
/> | ||
<Button | ||
type="submit" | ||
className="ml-4 flex-none" | ||
onClick={() => { | ||
sendMessage(input) | ||
setInput('') | ||
}} | ||
> | ||
Say | ||
</Button> | ||
</div> | ||
) | ||
|
||
export function Chat() { | ||
const [messages, setMessages] = useState<ChatGPTMessage[]>(initialMessages) | ||
const [input, setInput] = useState('') | ||
const [loading, setLoading] = useState(false) | ||
const [cookie, setCookie] = useCookies([COOKIE_NAME]) | ||
|
||
useEffect(() => { | ||
if (!cookie[COOKIE_NAME]) { | ||
// generate a semi random short id | ||
const randomId = Math.random().toString(36).substring(7) | ||
setCookie(COOKIE_NAME, randomId) | ||
} | ||
}, [cookie, setCookie]) | ||
|
||
// send message to API /api/chat endpoint | ||
const sendMessage = async (message: string) => { | ||
setLoading(true) | ||
const newMessages = [ | ||
...messages, | ||
{ role: 'user', content: message } as ChatGPTMessage, | ||
] | ||
setMessages(newMessages) | ||
const last10messages = newMessages.slice(-10) // remember last 10 messages | ||
|
||
const response = await fetch('/api/chat', { | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
body: JSON.stringify({ | ||
messages: last10messages, | ||
user: cookie[COOKIE_NAME], | ||
}), | ||
}) | ||
|
||
console.log('Edge function returned.') | ||
|
||
if (!response.ok) { | ||
throw new Error(response.statusText) | ||
} | ||
|
||
// This data is a ReadableStream | ||
const data = response.body | ||
if (!data) { | ||
return | ||
} | ||
|
||
const reader = data.getReader() | ||
const decoder = new TextDecoder() | ||
let done = false | ||
|
||
let lastMessage = '' | ||
|
||
while (!done) { | ||
const { value, done: doneReading } = await reader.read() | ||
done = doneReading | ||
const chunkValue = decoder.decode(value) | ||
|
||
lastMessage = lastMessage + chunkValue | ||
|
||
setMessages([ | ||
...newMessages, | ||
{ role: 'assistant', content: lastMessage } as ChatGPTMessage, | ||
]) | ||
|
||
setLoading(false) | ||
} | ||
} | ||
|
||
return ( | ||
<div className="rounded-2xl border-zinc-100 lg:border lg:p-6"> | ||
{messages.map(({ content, role }, index) => ( | ||
<ChatLine key={index} role={role} content={content} /> | ||
))} | ||
|
||
{loading && <LoadingChatLine />} | ||
|
||
{messages.length < 2 && ( | ||
<span className="mx-auto flex flex-grow text-gray-600 clear-both"> | ||
Type a message to start the conversation | ||
</span> | ||
)} | ||
<InputMessage | ||
input={input} | ||
setInput={setInput} | ||
sendMessage={sendMessage} | ||
/> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import clsx from 'clsx' | ||
import Balancer from 'react-wrap-balancer' | ||
|
||
// wrap Balancer to remove type errors :( - @TODO - fix this ugly hack | ||
const BalancerWrapper = (props: any) => <Balancer {...props} /> | ||
|
||
type ChatGPTAgent = 'user' | 'system' | 'assistant' | ||
|
||
export interface ChatGPTMessage { | ||
role: ChatGPTAgent | ||
content: string | ||
} | ||
|
||
// loading placeholder animation for the chat line | ||
export const LoadingChatLine = () => ( | ||
<div className="flex min-w-full animate-pulse px-4 py-5 sm:px-6"> | ||
<div className="flex flex-grow space-x-3"> | ||
<div className="min-w-0 flex-1"> | ||
<p className="font-large text-xxl text-gray-900"> | ||
<a href="#" className="hover:underline"> | ||
AI | ||
</a> | ||
</p> | ||
<div className="space-y-4 pt-4"> | ||
<div className="grid grid-cols-3 gap-4"> | ||
<div className="col-span-2 h-2 rounded bg-zinc-500"></div> | ||
<div className="col-span-1 h-2 rounded bg-zinc-500"></div> | ||
</div> | ||
<div className="h-2 rounded bg-zinc-500"></div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
) | ||
|
||
// util helper to convert new lines to <br /> tags | ||
const convertNewLines = (text: string) => | ||
text.split('\n').map((line, i) => ( | ||
<span key={i}> | ||
{line} | ||
<br /> | ||
</span> | ||
)) | ||
|
||
export function ChatLine({ role = 'assistant', content }: ChatGPTMessage) { | ||
if (!content) { | ||
return null | ||
} | ||
const formatteMessage = convertNewLines(content) | ||
|
||
return ( | ||
<div | ||
className={ | ||
role != 'assistant' ? 'float-right clear-both' : 'float-left clear-both' | ||
} | ||
> | ||
<BalancerWrapper> | ||
<div className="float-right mb-5 rounded-lg bg-white px-4 py-5 shadow-lg ring-1 ring-zinc-100 sm:px-6"> | ||
<div className="flex space-x-3"> | ||
<div className="flex-1 gap-4"> | ||
<p className="font-large text-xxl text-gray-900"> | ||
<a href="#" className="hover:underline"> | ||
{role == 'assistant' ? 'AI' : 'You'} | ||
</a> | ||
</p> | ||
<p | ||
className={clsx( | ||
'text ', | ||
role == 'assistant' ? 'font-semibold font- ' : 'text-gray-400' | ||
)} | ||
> | ||
{formatteMessage} | ||
</p> | ||
</div> | ||
</div> | ||
</div> | ||
</BalancerWrapper> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
/// <reference types="next" /> | ||
/// <reference types="next/image-types/global" /> | ||
|
||
// NOTE: This file should not be edited | ||
// see https://nextjs.org/docs/basic-features/typescript for more information. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
{ | ||
"name": "ai-chatgpt", | ||
"repository": "https://github.com/vercel/examples.git", | ||
"license": "MIT", | ||
"private": true, | ||
"scripts": { | ||
"dev": "next dev", | ||
"build": "next build", | ||
"start": "next start", | ||
"lint": "next lint" | ||
}, | ||
"dependencies": { | ||
"@vercel/analytics": "^0.1.11", | ||
"@vercel/examples-ui": "^1.0.5", | ||
"clsx": "^1.2.1", | ||
"eventsource-parser": "^0.1.0", | ||
"next": "latest", | ||
"react": "latest", | ||
"react-cookie": "^4.1.1", | ||
"react-dom": "latest", | ||
"react-wrap-balancer": "^0.1.5" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "^17.0.45", | ||
"@types/react": "latest", | ||
"autoprefixer": "^10.4.14", | ||
"eslint": "^8.36.0", | ||
"eslint-config-next": "^12.3.4", | ||
"postcss": "^8.4.21", | ||
"tailwindcss": "^3.2.7", | ||
"turbo": "^1.8.3", | ||
"typescript": "^4.9.5" | ||
} | ||
} |
Oops, something went wrong.
af11626
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
ai-gpt3-chatbot – ./
ai-gpt3-chatbot-git-main-pointersoftware.vercel.app
ai-gpt3-chatbot-iota-two.vercel.app
ai-gpt3-chatbot-pointersoftware.vercel.app