Skip to content
This repository was archived by the owner on Feb 6, 2024. It is now read-only.

Commit 40cd167

Browse files
feat: stream responses (#6)
1 parent 23f2fcc commit 40cd167

12 files changed

Lines changed: 138 additions & 348 deletions

File tree

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@
1515
"@types/react-dom": "18.2.5",
1616
"@types/react-syntax-highlighter": "15.5.7",
1717
"@types/uuid": "9.0.2",
18+
"ai": "2.1.8",
1819
"autoprefixer": "10.4.14",
1920
"date-fns": "2.30.0",
2021
"eslint": "8.43.0",
2122
"eslint-config-next": "13.4.6",
2223
"github-markdown-css": "5.2.0",
2324
"gpt-3-encoder": "1.1.4",
2425
"next": "13.4.6",
25-
"openai": "3.3.0",
26+
"openai-edge": "1.1.0",
2627
"postcss": "8.4.24",
2728
"react": "18.2.0",
2829
"react-dom": "18.2.0",

src/app/api/chat/route.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Configuration, OpenAIApi } from "openai-edge"
2+
import { OpenAIStream, StreamingTextResponse } from "ai"
3+
4+
const config = new Configuration({
5+
apiKey: process.env.OPENAI_API_KEY,
6+
organization: process.env.OPENAI_API_ORG_ID,
7+
})
8+
9+
const openai = new OpenAIApi(config)
10+
11+
export const runtime = "edge"
12+
13+
export async function POST(req: Request) {
14+
const { messages } = await req.json()
15+
16+
const response = await openai.createChatCompletion({
17+
messages,
18+
stream: true,
19+
model: "gpt-3.5-turbo-16k-0613",
20+
})
21+
22+
const stream = OpenAIStream(response)
23+
24+
return new StreamingTextResponse(stream)
25+
}

src/app/api/openai/route.ts

Lines changed: 0 additions & 9 deletions
This file was deleted.

src/components/chat/form.tsx

Lines changed: 6 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,25 @@
11
"use client"
22

3-
import { useState } from "react"
4-
import { useForm } from "react-hook-form"
5-
import type { ChatCompletionRequestMessage } from "openai"
3+
import { useChat } from "ai/react"
64
import ReactTextareaAutosize from "react-textarea-autosize"
75

86
import SendIcon from "../assets/send-icon"
9-
import { useChat, type Message } from "@/hooks/use-chat"
107

11-
type FormData = { prompt: string }
12-
13-
async function getOpenAIResponse(messages: ChatCompletionRequestMessage[]) {
14-
const response = await fetch("/api/openai", {
15-
method: "POST",
16-
body: JSON.stringify(messages),
17-
headers: { "Content-Type": "application/json" },
18-
})
19-
20-
if (response?.ok) {
21-
return response.json()
22-
} else {
23-
throw response?.statusText
24-
}
25-
}
26-
27-
async function tokenizePrompt(prompt: string) {
28-
const response = await fetch("/api/tokenize", {
29-
method: "POST",
30-
body: prompt,
31-
})
32-
33-
if (response?.ok) {
34-
return response.json()
35-
} else {
36-
throw response?.statusText
37-
}
38-
}
39-
40-
const createMessage = async (prompt: string): Promise<Message> => {
41-
const tokenizedPrompt = await tokenizePrompt(prompt)
42-
43-
return {
44-
data: { role: "user", content: prompt },
45-
metadata: { creationDate: new Date(), tokens: tokenizedPrompt.length },
46-
}
47-
}
48-
49-
const Form = () => {
50-
const [prompt, setPrompt] = useState<string>("")
51-
const { register, handleSubmit } = useForm<FormData>()
52-
const { messages, addMessage, setStatus } = useChat()
53-
54-
const onSubmit = async (data: FormData) => {
55-
const message = await createMessage(prompt)
56-
57-
addMessage(message)
58-
setStatus("loading")
59-
setPrompt("")
60-
61-
try {
62-
const trimMessages = [...messages, message].map(({ data }) => data)
63-
const { result } = await getOpenAIResponse(trimMessages)
64-
const botMessage = {
65-
data: result,
66-
metadata: { creationDate: new Date() },
67-
} as Message
68-
69-
addMessage(botMessage)
70-
setStatus("ready")
71-
} catch (error) {
72-
console.log(error)
73-
}
74-
}
8+
const Form = ({ id }: { id: string }) => {
9+
const { input, handleInputChange, handleSubmit } = useChat({ id })
7510

7611
return (
7712
<div className="form-container">
78-
<form onSubmit={handleSubmit(onSubmit)}>
13+
<form onSubmit={handleSubmit}>
7914
<ReactTextareaAutosize
8015
rows={1}
8116
autoFocus
8217
minRows={1}
8318
maxRows={8}
84-
value={prompt}
19+
value={input}
8520
cacheMeasurements
86-
{...register("prompt")}
21+
onChange={handleInputChange}
8722
placeholder="Envoyez un message..."
88-
onChange={({ target: { value } }: { target: { value: string } }) =>
89-
setPrompt(value)
90-
}
9123
/>
9224
<button type="submit">
9325
<SendIcon />

src/components/chat/index.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
import Form from "./form"
22
import Messages from "./messages"
3-
import { ChatProvider } from "@/hooks/use-chat"
43

54
import "./styles.css"
65

76
export default function Chat({ theme, id }: { theme: string; id: string }) {
87
return (
98
<div className="chat">
10-
<ChatProvider threadId={id}>
11-
<Messages />
12-
<Form />
13-
</ChatProvider>
9+
<Messages id={id} />
10+
<Form id={id} />
1411
</div>
1512
)
1613
}

src/components/chat/loading-message.tsx

Lines changed: 0 additions & 18 deletions
This file was deleted.

src/components/chat/message.tsx

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,10 @@ import SyntaxHighlighter, {
88
} from "react-syntax-highlighter"
99
import { tomorrowNight } from "react-syntax-highlighter/dist/cjs/styles/hljs"
1010

11-
import type { Message } from "@/hooks/use-chat"
11+
import { type Message } from "ai/react"
1212

13-
const Message = ({
14-
message: {
15-
data: { content },
16-
metadata: { creationDate, tokens },
17-
},
18-
}: {
19-
message: Message
20-
}) => {
21-
const date =
22-
creationDate instanceof Date ? creationDate : new Date(creationDate)
13+
const Message = ({ message: { content, createdAt } }: { message: Message }) => {
14+
const date = createdAt instanceof Date ? createdAt : new Date()
2315

2416
return (
2517
<div className="message">
@@ -49,7 +41,7 @@ const Message = ({
4941
/>
5042
</div>
5143
<div className="info">
52-
{tokens && <div>tokens: {tokens}</div>}
44+
{/* {tokens && <div>tokens: {tokens}</div>} */}
5345
<div className="flex-1 text-right">{format(date, "PPPppp")}</div>
5446
</div>
5547
</div>

src/components/chat/messages.tsx

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
"use client"
22

3-
import { v4 as uuid } from "uuid"
3+
import { useChat } from "ai/react"
44
import React, { useRef, useEffect } from "react"
55

66
import Message from "./message"
7-
import { useChat } from "@/hooks/use-chat"
8-
import LoadingMessage from "./loading-message"
97
import UserIcon from "@/components/assets/user-icon"
108
import RobotIcon from "@/components/assets/robot-icon"
119

1210
const SCROLL_DELAY = 300
1311

14-
const Messages = () => {
15-
const { messages, status } = useChat()
12+
const Messages = ({ id }: { id: string }) => {
1613
const messagesEndRef = useRef<HTMLLIElement | null>(null)
1714

15+
const { messages, setMessages } = useChat({ id })
16+
17+
useEffect(() => {
18+
const messages = JSON.parse(localStorage.getItem(id) || "[]")
19+
setMessages(messages)
20+
}, [id, setMessages])
21+
1822
const scrollToBottom = () => {
1923
setTimeout(() => {
2024
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" })
@@ -25,23 +29,22 @@ const Messages = () => {
2529
scrollToBottom()
2630
}, [messages])
2731

32+
useEffect(() => {
33+
localStorage.setItem(id, JSON.stringify(messages))
34+
}, [id, messages])
35+
2836
return (
2937
<ul className="messages">
3038
{messages.map((message, i) => (
31-
<li key={`message-${uuid()}`} className={message.data.role}>
39+
<li key={message.id} className={message.role}>
3240
<div className="message-container">
3341
<div className="icon">
34-
{message.data.role === "user" ? <UserIcon /> : <RobotIcon />}
42+
{message.role === "user" ? <UserIcon /> : <RobotIcon />}
3543
</div>
3644
<Message message={message} />
3745
</div>
3846
</li>
3947
))}
40-
{status === "loading" && (
41-
<li>
42-
<LoadingMessage />
43-
</li>
44-
)}
4548
<li ref={messagesEndRef}></li>
4649
</ul>
4750
)

src/components/timer.tsx

Lines changed: 0 additions & 39 deletions
This file was deleted.

0 commit comments

Comments
 (0)