opean ai의 api를 이용해서 chatBot 형식의 기능 구현
open ai에 로그인 해서 좌상단 프로필 클릭하고 view api key를 입력해서 api키를 발급한다.
이때 발급한 키는 다시 확인 할 수 없기 때문에 잘 보관해야 한다.
https://platform.openai.com/docs/libraries/node-js-library
를 들어가서 언어별로 라이브러리 설치 명령어를 확인 할 수 있다.
node.js이기 때문에 $ npm install openai
를 해서 프로젝트에 설치한다.
우선 chatBot 파일을 만들어서 사용할 것이다.
타입스크립트를 적용했지만 타입 확인하기가 어려워 대충 any로 넣었고 css는 생략
우선 유저의 질문을 받을 state, chat을 담을 수 있는 state를 만들어 준다.
// chatBot.tsx
const chatBot = () => {
// 유저의 질문을 담을 state
const [questions, setQuestions] = useState<any>();
// 채팅을 위한 state
const [chat, setChat] = useState<any>([]);
}
이후 chatgpt와 통신할 함수를 만들어 준다.
const chatAi = async (data: string) => {
try {
// axios를 이용해서 chatgpt와 통신
const pos = await axios.post(
"https://api.openai.com/v1/completions",
// docs복사 prompt에 내가 한 질문 입력
{
model: "text-davinci-003",
prompt: `${data}`,
temperature: 0.9,
max_tokens: 521,
top_p: 1,
frequency_penalty: 0,
presence_penalty: 0.6,
stop: [" Human:", " AI:"],
},
// 발급받은 api키 env로 입력
{
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + String(process.env.NEXT_PUBLIC_OPEN_API),
},
},
);
console.log(pos);
// chat state에 기존의 값 + 대답 저장
// id는 맵돌릴때 필요해서 입력
setChat((prev: any) => [
...prev,
{ text: pos.data.choices[0].text, id: pos.data.id },
]);
// 답변을 기다리는 동안 제어해줄 컴포넌트
setWaitAnswer((prev) => !prev);
} catch (error) {
console.log(error);
setWaitAnswer((prev) => !prev);
alert("오류가 발생하였습니다.");
// 에러가 발생하였을 경우 질문과 채팅 내역 모두 제거
setQuestions("");
setChat([]);
}
};
data로 넣어줄 인자는 질문을 담은 state인 questions이다
pos 함수를 실행시키면
config
:
{transitional: {…}, adapter: Array(2), transformRequest: Array(1), transformResponse: Array(1), timeout: 0, …}
data
:
{id: 'cmpl-6fmk4ho5o1lecQcTRST0sf2u1Am0p', object: 'text_completion', created: 1675416832, model: 'text-davinci-003', choices: Array(1), …}
headers
:
AxiosHeaders {cache-control: 'no-cache, must-revalidate', content-length: '285', content-type: 'application/json'}
request
:
XMLHttpRequest {onreadystatechange: null, readyState: 4, timeout: 0, withCredentials: false, upload: XMLHttpRequestUpload, …}
status
:
200
statusText
:
""
라는 결과값을 가지고 여기서 필요한 것은 data.choices[0].text
이며 질문에 대한 답변이다.
마지막으로 input과 button을 이용해서 제어하는 함수이다.
// input값을 questions state에 입력
const questionsHandler = (e: ChangeEvent<HTMLInputElement>) => {
setQuestions(e.target.value);
};
// 질문 제출을 위한 버튼 제어 함수
const submitQuestion = () => {
// 질문이 없을 경우 실행 X
if (!questions) {
return null;
}
// 답변 기다리는 동안 로딩 보여줌
setWaitAnswer((prev) => !prev);
// chat state에 질문을 push
setChat((prev: any) => [...prev, { text: questions, id: uuidv4() }]);
// chatAi 함수 실행
chatAi(questions);
// 내가 한 질문 제거(input 비워주기)
setQuestions("");
};
// 버튼을 누르면 제출
const onKeyPress = (e: KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
submitQuestion();
}
};
import { flexBox } from "@src/utils/flexBox";
import theme from "@src/utils/theme";
// import { Configuration, OpenAIApi } from "openai";
import styled from "styled-components";
import axios from "axios";
import React, { ChangeEvent, KeyboardEvent, useState } from "react";
import { v4 as uuidv4 } from "uuid";
import Loading from "../../../../public/icon/loading.gif";
import Image from "next/image";
import Bot from "../../../../public/icon/botIcon.png";
import User from "../../../../public/icon/profile.png";
interface IProps {
isVisible: boolean;
chatBotHandler?: () => void;
}
const Title = styled.div`
${flexBox("row", "between", "center")}
width: 100%;
margin-bottom: 2rem;
font-size: 2rem;
font-weight: 700;
color: ${theme.colors.darkGray};
`;
const Main = styled.div<IProps>`
${flexBox("col", "center", "center")}
display: ${(props) => (props.isVisible ? "flex" : "none")};
width: 25%;
height: 70%;
padding: 3rem;
background-color: ${theme.colors.lightPurple};
border-radius: 16px;
position: absolute;
top: 50%;
transform: translateY(-50%);
z-index: 9;
box-shadow: 0px 0.4rem 0.4rem rgba(0, 0, 0, 0.25);
`;
const Box = styled.div`
width: 100%;
height: 100%;
padding: 2rem;
margin-bottom: 2rem;
background-color: ${theme.colors.white};
border-radius: 16px;
overflow: scroll;
`;
const ChatBox = styled.div`
${flexBox("row", "start", "start")}
margin-bottom: 2rem;
`;
const Chat = styled.div`
width: 100%;
height: 100%;
padding: 1rem;
font-size: 1.6rem;
color: ${theme.colors.white};
background-color: ${theme.colors.mainPurple};
border-radius: 16px;
margin-bottom: 1rem;
`;
const AnswerChat = styled.div`
${flexBox("row", "start", "center")}
width: 100%;
height: 100%;
margin-bottom: 1rem;
padding: 1rem;
font-size: 1.6rem;
background-color: ${theme.colors.lightPink};
border-radius: 16px;
`;
const LoadingBox = styled.div`
width: 2rem;
height: 2rem;
position: relative;
`;
const QuestionBox = styled.div`
${flexBox("row", "between", "center")}
width: 100%;
`;
const SubmitButton = styled.button`
padding: 1rem 2rem;
font-size: 1.6rem;
color: ${theme.colors.white};
background-color: ${theme.colors.deepPurple};
border-radius: 8px;
`;
const ChatProfileBox = styled.div`
width: 3rem;
height: 3rem;
position: relative;
margin-right: 1rem;
`;
const ChatUserProfileBox = styled.div`
width: 3rem;
height: 3rem;
position: relative;
margin-left: 1rem;
`;
const ChatProfile = styled(Image)``;
const LoadingIcon = styled(Image)``;
const ChatInput = styled.input`
width: 70%;
padding: 1.2rem;
font-size: 1.6rem;
border: none;
border-radius: 8px;
:disabled {
background-color: ${theme.colors.sliverGray};
}
`;
const ChatBot = ({ isVisible, chatBotHandler }: IProps) => {
const [questions, setQuestions] = useState<any>();
const [chat, setChat] = useState<any>([]);
const [waitAnswer, setWaitAnswer] = useState(false);
const chatAi = async (data: string) => {
try {
const pos = await axios.post(
"https://api.openai.com/v1/completions",
{
model: "text-davinci-003",
prompt: `${data}`,
temperature: 0.9,
max_tokens: 521,
top_p: 1,
frequency_penalty: 0,
presence_penalty: 0.6,
stop: [" Human:", " AI:"],
},
{
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + String(process.env.NEXT_PUBLIC_OPEN_API),
},
},
);
console.log(pos);
setChat((prev: any) => [
...prev,
{ text: pos.data.choices[0].text, id: pos.data.id },
]);
setWaitAnswer((prev) => !prev);
} catch (error) {
console.log(error);
setWaitAnswer((prev) => !prev);
alert("오류가 발생하였습니다.");
setQuestions("");
setChat([]);
}
};
const questionsHandler = (e: ChangeEvent<HTMLInputElement>) => {
setQuestions(e.target.value);
};
const submitQuestion = () => {
if (!questions) {
return null;
}
setWaitAnswer((prev) => !prev);
setChat((prev: any) => [...prev, { text: questions, id: uuidv4() }]);
chatAi(questions);
setQuestions("");
};
const onKeyPress = (e: KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
submitQuestion();
}
};
return (
<Main isVisible={isVisible}>
<Title>
<div></div>
ChatBot에게 질문해 보세요
<div style={{ cursor: "pointer" }} onClick={chatBotHandler}>
X
</div>
</Title>
<Box>
{chat.map((el: any, idx: number) => (
<React.Fragment key={el.id}>
{idx % 2 === 0 ? (
<ChatBox>
<Chat>{el.text}</Chat>
<ChatUserProfileBox>
<ChatProfile src={User} alt="User" fill={true} />
</ChatUserProfileBox>
</ChatBox>
) : (
<ChatBox>
<ChatProfileBox>
<ChatProfile src={Bot} alt="User" fill={true} />
</ChatProfileBox>
<AnswerChat>{el.text}</AnswerChat>
</ChatBox>
)}
</React.Fragment>
))}
</Box>
<QuestionBox>
<ChatInput
onChange={questionsHandler}
value={questions || ""}
onKeyDown={onKeyPress}
disabled={waitAnswer}
placeholder="챗봇에게 물어보기"
/>
<SubmitButton
type="submit"
onClick={submitQuestion}
disabled={waitAnswer}
>
{waitAnswer ? (
<LoadingBox>
<LoadingIcon src={Loading} alt="Loading" fill={true} />
</LoadingBox>
) : (
<>전송</>
)}
</SubmitButton>
</QuestionBox>
</Main>
);
};
export default ChatBot;