기존에 응답이 다 오면 화면에 보여주는 형식은 너무 오래 기다려야해서 사용자 경험에 좋지 않다고 느꼈다. 실제로 chatGPT 를 사용하면 응답을 stream 형식으로 준다. 관련 문서를 찾아보니 next.js 에서 stream 에 관련해 자세하게 설명해주고 있었다.
docs
openAI streaming in next.js
useChat로 chatting app 구현을 쉽게 해줄 수 있다.
const {
input, //사용자 input state를 따로 딸 필요 없이 input state를 불러올 수 있음
handleInputChange, //setInput(()=>e.target.value) 해줄 필요 없이 input state 저장
handleSubmit, //submit 함수를 따로 만들어줄 필요 없이 내장되어있음
isLoading,
messages, //아래 설명
} = useChat();
messages : 이게 제일 중요한데 기존에 setChatLogs state로 관리했던 message들을 객체로 깔끔하게 담아준다.
role, content, createdAt, id 을 출력함.
짱인 점은 messages 내 ai 대답을 담는 index.content에 글씨가 하나씩 담기는 점이다. 서버에서 글자를 하나씩 보내줘서 이걸 map돌려 streaming 해줄 수 있는것!
아래 이미지를 보면 처음엔 한글자만 담기다가 점점 다 담기는 것을 볼 수 있음!
useChat 을 써준다고 다 되는 것은 아님.
서버 측에서 동작할 API핸들러가 필요하다. app/chat.ts 파일을 작성해줬다.
(물론 api 파일명을 다른 걸로 설정도 가능! client 에서 useChat을 불러올 때 api root를 작성하면 됨)
//src/components/Chat.tsx
const { input, handleInputChange, handleSubmit, messages, isLoading } =
useChat({
api: 'api/파일명',
});
//app/chat.ts
import { Configuration, OpenAIApi } from "openai-edge";
import { OpenAIStream, StreamingTextResponse } from "ai";
export const runtime = "edge"; //추후 배포 환경 위해 작성
const config = new Configuration({
//openAI API를 사용하기 위한 설정
apiKey: process.env.OPENAI_API_KEY,
});
const openai = new OpenAIApi(config);
// POST localhost:3000/api/chat
export default async function POST(request: Request) {
const { messages } = await request.json();
const response = await openai.createChatCompletion({
model: "gpt-3.5-turbo",
stream: true, //streaming 을 위해 꼭 써야함
messages: [
{
role: "system",
content:
"You are a creative helpful assistant. You are a youtube creater.", //AI 가 어떤 시스템인지를 인지시켜 준다. 여기서는 크리에이티브 한 크리에이터로 설정
},
...messages,
],
});
//openAI API의 응답을 스트림 형식으로 처리하고,
const stream = await OpenAIStream(response);
// 해당 스트림을 클라이언트로 응답으로 반환
return new StreamingTextResponse(stream);
}
//src/components/Chat.tsx
<ul className={cx("feed")} ref={chatContainerRef}>
{messages.map((message: Message, index: number) => {
//주고받은 메세지들을 담은 messages 배열을 map 돌려서 채팅 형식으로 구성
return (
<li
key={message.id + index}
className={cx("chatting", { user: message.role === "user" })}
>
{message.role === "assistant" && <div className={cx("bot-icon")}></div>}
<div className={cx("multiple-chats")}>
{message.content.split("\n").map((text: string, index: number) => {
if (text === "") {
// 문단이 달라지면 다른 말풍선으로 보여주기 위함
return (
<p className={cx("nbsp")} key={message.id + index}>
</p>
);
} else {
return (
<p
className={cx("chat", {
user: message.role === "user",
})}
key={message.id + index}
>
{text}
</p>
);
}
})}
</div>
</li>
);
})}
</ul>
const handleResizeHeight = useCallback(() => {
if (!textRef.current) {
return;
}
textRef.current.style.height = "100px";
textRef.current.style.height = textRef.current.scrollHeight + "px";
}, []);
useEffect(() => {
if (chatContainerRef.current) {
chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight;
}
}, [messages]);
ui 도 찐처럼 수정을 해봤다..!
문단별로 짤라서 나오는 것도 잘 되고
속도도 매우 빠름..
아주 마음에 든다
하지만 .. 최신 정보를 모르는 쳇지피티 ,,
다음엔 langchain을 사용해서 업데이트 해 볼 예정이다!