김현빈의 개발새발
리액트에서 API 사용하지 않고 챗봇 구현하기 본문
프로젝트를 진행하던 중, 챗봇 기능을 넣고 싶어서 리액트에서 챗봇 기능을 구현할수있는 여러가지의 챗봇 API가 있었지만, 필요로 한 기능이 사용자와 다양한 상호작용이나 기능을 수행하는 챗봇이 아닌 사용자가 필요로 하는 정보를 입력하면 이 입력된 정보를 토대로 저장되어 있는 데이터베이스의 정보를 출력시키는 수준정도만 필요로 했기 때문에 직접 만들어 보았다.
필자가 구현하려는 범위는 저장되어 있는 강아지, 고양이의 종별 정보를 사용자가 입력한 정보(종명)을 토대로 검색을 하여 해당하는 종의 정보를 출력시키는 기능과 원하는 날짜 (오늘, 내일, 이번주)에 해당하는 날씨와 산책지수 (반려동물과 산책을 하기 좋은 날씨인지 아닌지 알려주는 지수) 를 출력시켜주는 기능 정도의 범위이다.
데이터베이스에 저장되어 있는 정보를 뽑아오는 AXIOS 코드
// 고양이 검색
catSearch: async (koreanName) => {
return await AxiosInstance.get(
MUNG_HOST + `/api/cats/detail/${koreanName}`
);
},
// 강아지 검색
dogSearch: async (koreanName) => {
return await AxiosInstance.get(
MUNG_HOST + `/api/dogs/detail/${koreanName}`
);
},
챗봇 초기 셋팅
const addMessage = (text, sender = "user") => {
setMessages((prevMessages) => [...prevMessages, { text, sender }]);
};
사용자 입력 메시지를 메시지 목록에 추가하는 함수, text는 채팅창에 추가되는 메시지, sender는 메시지의 송신자를 나타냄
const handleSendMessage = async () => {
if (inputText.trim() === "") return;
// 사용자가 입력한 메시지를 채팅창에 추가
addMessage(inputText, "user");
// 사용자 입력을 처리하고 챗봇 응답 생성
const response = await generateBotResponse(inputText);
// 챗봇 응답을 채팅창에 추가
addMessage(response, "bot");
// 입력창 초기화
setInputText("");
};
사용자가 메시지를 보낼 때, 호출되는 함수로 사용자의 입력을 받아 사용자가 입력한 메시지를 채팅창에 추가하고 이를 bot의 메시지로 반환하기 위해 generateBotResponse에 inputText (사용자가 입력한 메시지)에 입력시킴
useEffect(() => {
chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight;
}, [messages]);
이 코드는 React의 useEffect 훅을 사용하여 메시지 목록이 업데이트될 때마다 채팅창 컨테이너를 자동으로 최하단으로 스크롤하는 역할을 한다. 이 useEffect 훅은 messages라는 종속성 배열을 갖고 있다. 이 배열에 있는 값이 변경될 때마다 useEffect의 콜백 함수가 실행된다. 즉, messages 배열이 변경될 때마다 스크롤이 채팅창 맨 아래로 이동하게 된다.
챗봇 실행 시, 초기 랜더링 되는 화면
<Container>
<div className="chat">
{showWelcome && (
<WelcomeContainer>
<p className="Welcome">
PETMEMOIR의 챗봇입니다. 어떤 도움이 필요하신가요?
</p>
<WelcomeButtons>
<WelcomeButton onClick={() => handleWelcomeButtonClick(1)}>
묘종검색
</WelcomeButton>
<WelcomeButton onClick={() => handleWelcomeButtonClick(2)}>
견종검색
</WelcomeButton>
<WelcomeButton onClick={() => handleWelcomeButtonClick(3)}>
멍냥일기
</WelcomeButton>
<WelcomeButton onClick={() => handleWelcomeButtonClick(4)}>
고객센터
</WelcomeButton>
</WelcomeButtons>
</WelcomeContainer>
)}
<ChatboxContainer ref={chatContainerRef}>
{messages.map((message, index) => (
<div key={index} style={{ paddingBottom: "8px" }}>
{message.sender === "bot" ? (
<Boxleft>
<BotMessage>
<strong>동물박사:</strong> {message.text}
</BotMessage>
</Boxleft>
) : (
<Boxright>
<UserMessage>
<strong>사용자:</strong>
{message.text}
</UserMessage>
</Boxright>
)}
</div>
))}
</ChatboxContainer>
<InputContainer>
<Input
className="chatbox"
placeholder="궁금한 정보를 자유롭게 물어보세요"
type="text"
value={inputText}
onChange={handleInputChange}
/>
<SendButton className="sendbtn" onClick={handleSendMessage}>
전송
</SendButton>
</InputContainer>
</div>
</Container>
환영 메시지에서 버튼 클릭을 처리하는 함수, 각각의 버튼 마다 묘종도감, 견종도감, 멍냥일기, 고객센터에 대응하는 번호를 가지고 있음
// 환영 메시지에서 원하는 기능을 선택하게 하고 선택한 기능에 맞춘 함수를 실행시키기 위해 buttonNumber에 기능에 해당하는 번호를 입력시킴
const handleWelcomeButtonClick = (buttonNumber) => {
console.log(buttonNumber);
// 버튼에 따라 다른 동작 수행
if (buttonNumber === 1) {
// 1번 버튼 클릭 시 동작
addMessage("검색할 묘종의 이름을 입력하세요.", "bot");
} else if (buttonNumber === 2) {
// 2번 버튼 클릭 시 동작
addMessage("검색할 견종의 이름을 입력하세요.", "bot");
} else if (buttonNumber === 3) {
// 3번 버튼 클릭 시 동작.
addMessage("멍냥일기 페이지로 이동합니다.", "bot");
setTimeout(() => {
navigate("/diy");
}, 1000);
} else if (buttonNumber === 4) {
// 4번 버튼 클릭 시 동작
addMessage("고객센터 페이지로 이동합니다.", "bot");
setTimeout(() => {
navigate("/service");
}, 1000);
}
// 버튼이 클릭되면 환영 메시지와 버튼을 숨김
setButtonClicked(true);
setShowWelcome(false);
// 선택한 기능에 맞춰서 응답해주기 위해 기능에 해당하는 번호를 저장
setClickedButtonNumber(buttonNumber);
};
선택한 기능 (buttonNumber)에 해당하는 함수를 실행시켜, 챗봇의 응답을 생성하는 함수
// 챗봇 응답 생성 함수
const generateBotResponse = async (InputText) => {
console.warn(InputText);
// 버튼이 클릭되지 않았을 때는 빈 응답 반환
if (!buttonClicked) {
return "도움이 필요한 항목을 선택하여 다시 질문해주세요";
}
// 간단한 패턴 매칭을 통해 사용자 입력에 따른 응답 생성
const lowercaseInput = InputText.toLowerCase();
if (clickedButtonNumber === 1) {
try {
const response = await AxiosApi.catSearch(lowercaseInput);
// API 응답에 따른 동작 수행
const catInfo = response.data;
console.log(response.data);
// '치와와'에 관한 정보와 버튼을 포함한 응답 생성
return (
<chat1>
{catInfo.korean_name}에 관한 정보입니다.
<ul style={{ listStyleType: "none" }}>
<li>
<Image src={catInfo.image_link} alt={catInfo.name} />
</li>
<li>크기: {catInfo.length}</li>
<li>
체중: {catInfo.min_weight}~{catInfo.max_weight} 파운드
</li>
</ul>{" "}
</chat1>
);
} catch (error) {
console.error("Error fetching cat information:", error);
return "해당하는 묘종을 찾을 수 없습니다.";
}
} else if (clickedButtonNumber === 2) {
try {
const response = await AxiosApi.dogSearch(lowercaseInput);
// API 응답에 따른 동작 수행
const dogInfo = response.data;
console.log(response.data);
// '치와와'에 관한 정보와 버튼을 포함한 응답 생성
return (
<chat1>
{dogInfo.korean_name}에 관한 정보입니다.
<ul style={{ listStyleType: "none" }}>
<li>
<Image src={dogInfo.image_link} alt={dogInfo.name} />
</li>
<li>
기대수명: {dogInfo.min_life_expectancy}~
{dogInfo.max_life_expectancy}년
</li>
<li>
수컷 크기: {dogInfo.min_height_male}~{dogInfo.max_height_male}{" "}
인치
</li>
<li>
암컷 크기: {dogInfo.min_height_female}~
{dogInfo.max_height_female} 인치
</li>
<li>
수컷 체중: {dogInfo.min_weight_male}~{dogInfo.max_weight_male}{" "}
파운드
</li>
<li>
암컷 체중: {dogInfo.min_weight_female}~
{dogInfo.max_weight_female} 파운드
</li>
</ul>{" "}
</chat1>
);
} catch (error) {
console.error("Error fetching cat information:", error);
return "해당하는 견종을 찾을 수 없습니다.";
}
} else if (clickedButtonNumber === 3) {
// 3번 버튼에 대한 응답 처리
} else if (clickedButtonNumber === 4) {
// 4번 버튼에 대한 응답 처리
}
};
실제 구현 화면
이외의 묘종검색도 같은 형식으로 출력되고, 멍냥일기, 고객센터는 해당 페이지로 넘어가는 기능을 하게 구현을 해보았다.
아쉬운 점은 날씨와 산책지수를 조회하는 기능도 넣으려고 했는데 프로젝트의 진행과 남은시간 부족으로... 견종, 묘종 검색의 기능만 구현을 한 것이 아쉽고, 추후에 날씨와 산책지수를 조회하는 기능도 추가 해볼 예정이다.