[next.js] open ai Api를 이용해 chatBot 만들기

적자생존·2023년 2월 5일
2

next.js

목록 보기
4/6

1. 구현하고자 하는 기능

opean ai의 api를 이용해서 chatBot 형식의 기능 구현

2. open ai api key 발급하기

open ai에 로그인 해서 좌상단 프로필 클릭하고 view api key를 입력해서 api키를 발급한다.

이때 발급한 키는 다시 확인 할 수 없기 때문에 잘 보관해야 한다.

3. 라이브러리 설치

https://platform.openai.com/docs/libraries/node-js-library
를 들어가서 언어별로 라이브러리 설치 명령어를 확인 할 수 있다.

node.js이기 때문에 $ npm install openai를 해서 프로젝트에 설치한다.

4. 구현하기

우선 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();
    }
  };

5. 전체 코드

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;
profile
적는 자만이 생존한다.

0개의 댓글