Type something to search...

11. AI 챗봇 연동하기

요약

Gemini CLI로 구현하기 — AI 챗봇 컴포넌트

  1. npx gemini
  2. 1
    src/components/Chatbot.jsx를 작성해줘. 화면 우하단에 고정된 플로팅 버튼을 클릭하면 채팅창이 열리는 컴포넌트야. useState로 열림/닫힘, 메시지 목록, 입력값, 로딩 상태를 관리하고, useRef로 새 메시지가 오면 자동 스크롤해줘. 메시지 전송 시 [백엔드 URL]/chat 엔드포인트에 fetch로 POST 요청을 보내줘. Tailwind CSS를 사용해줘.
  3. 사용 가이드: [백엔드 URL]을 배포된 서버 주소로 바꾼다(예: https://my-api.onrender.com). 로컬 테스트 시에는 http://localhost:8000으로 바꾼다.

1. 챗봇 연동 개요

이번 편에서는 GOFLEX 프로젝트에 AI 챗봇을 연동한다. 플로팅 버튼과 채팅창이 하나의 Chatbot.jsx 컴포넌트에 들어 있고, App.jsx에서 바로 사용한다.

순서만들 기능핵심 개념
1단계Chatbot.jsx — import + 상태useState, useRef
2단계자동 스크롤useRef, scrollIntoView
3단계메시지 전송 함수fetch, try/catch/finally
4단계JSX — 플로팅 버튼 + 채팅창Tailwind 말풍선, 조건부 렌더링
5단계App.jsx에 연결import, <Chatbot />

정보

챗봇 백엔드 서버는 별도 프로젝트로 Python + FastAPI + HuggingFace로 구축하여 Render에 배포한 상태여야 한다.


2. 1단계 — import + 상태 변수

src/components/ 폴더 안에 Chatbot.jsx 파일을 새로 만들고 아래 코드를 입력한다. 이 파일 하나에 플로팅 버튼(화면 우하단에 떠 있는 동그란 버튼)과 채팅창이 모두 들어 있다.

src/components/Chatbot.jsx
1
import { useState, useRef, useEffect } from "react";
2
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3
import { faComment, faXmark } from "@fortawesome/free-solid-svg-icons";
4
import { Button } from "./UI.jsx";
5
6
const BACKEND = "https://cbot-rfcl.onrender.com/chat";
7
8
export default function Chatbot() {
9
const [open, setOpen] = useState(false);
10
const [messages, setMessages] = useState([
11
{ role: "bot", text: "안녕하세요! 영화에 대해 무엇이든 물어보세요" },
12
]);
13
const [input, setInput] = useState("");
14
const [loading, setLoading] = useState(false);
15
const bottomRef = useRef(null);
설명
1useRef(유즈레프) — DOM 요소를 직접 참조하는 훅이다. 여기서는 메시지 목록의 맨 아래를 가리키는 데 사용한다.
3faComment(말풍선), faXmark(X) 아이콘이다.
5챗봇 백엔드 서버 주소이다. 본인의 Render 배포 주소로 변경한다.
7export default — 이 컴포넌트는 파일의 대표 내보내기이다. App.jsx에서 import Chatbot으로 가져온다.
8open — 채팅창의 열림/닫힘 상태이다.
9-11messages — 대화 목록이다. 초기값에 봇의 인사 메시지를 넣어두면 채팅창을 열었을 때 바로 안내 메시지가 보인다.
14bottomRef — 메시지 목록의 맨 아래 빈 div를 가리키는 참조이다. 새 메시지가 추가되면 이 위치로 자동 스크롤한다.

useRef는 렌더링과 무관하게 값을 유지하거나, DOM 요소를 직접 참조할 때 사용하는 훅이다.

1
const bottomRef = useRef(null); // 참조 생성
2
// ...
3
<div ref={bottomRef} /> // DOM 요소에 연결
4
// ...
5
bottomRef.current // 연결된 실제 DOM 요소

useState와 달리 값이 바뀌어도 리렌더링이 발생하지 않는다.


3. 2단계 — 자동 스크롤

같은 Chatbot.jsx 파일에서, const bottomRef = useRef(null) 줄 바로 아래에 이어서 작성한다. 채팅 앱을 쓸 때 새 메시지가 오면 자동으로 스크롤이 내려가는 것을 본 적이 있을 것이다. 그 기능을 만드는 코드이다.

src/components/Chatbot.jsx
1
useEffect(() => {
2
if (open) bottomRef.current?.scrollIntoView({ behavior: "smooth" });
3
}, [messages, open]);
설명
2scrollIntoView(스크롤인투뷰) — 해당 DOM 요소가 화면에 보이도록 자동 스크롤한다. \{ behavior: "smooth" \}는 부드럽게 스크롤하라는 옵션이다.
2bottomRef.current?. — 옵셔널 체이닝이다. bottomRef.current가 아직 null이면 에러 없이 넘어간다.
3[messages, open] — 메시지가 추가되거나 채팅창이 열릴 때마다 실행된다.

새 메시지가 도착하면 채팅창이 자동으로 맨 아래로 스크롤되므로, 사용자가 수동으로 내릴 필요가 없다.


4. 3단계 — 메시지 전송 함수

같은 Chatbot.jsx 파일에서, useEffect 블록 바로 아래에 이어서 두 개의 함수를 작성한다. sendMessage는 메시지를 서버에 보내고 응답을 받는 함수이고, handleKeyDown은 Enter 키를 누르면 전송하는 함수이다.

1
try {
2
// 성공할 수도 있는 코드
3
} catch {
4
// 실패했을 때 실행되는 코드
5
} finally {
6
// 성공이든 실패든 반드시 실행되는 코드
7
}
블록실행 조건용도
try항상 실행 시도API 호출, 데이터 처리
catchtry에서 에러 발생 시에러 메시지 표시, 대체 동작
finally항상 (성공/실패 무관)로딩 상태 초기화, 정리 작업

이전 교안(5편)에서는 try/catch만 사용했다. finally를 추가하면 setLoading(false)trycatch 양쪽에 중복 작성할 필요가 없어진다.


5. 4단계 — JSX 렌더링

같은 Chatbot.jsx 파일에서, handleKeyDown 함수 바로 아래에 return문을 작성한다. 코드가 길기 때문에 세 부분으로 나누어 설명한다. 탭을 눌러 각 부분을 확인하면서 순서대로 이어 붙인다.


6. 5단계 — App.jsx에 연결

src/App.jsx를 수정한다. 두 곳을 변경한다.

6.1. import 추가

기존 import 영역에 한 줄을 추가한다.

src/App.jsx (import 추가)
1
import Chatbot from "./components/Chatbot.jsx";

요약

Chatbotexport default로 내보냈으므로 중괄호 \{ \} 없이 가져온다. 다른 컴포넌트(Header, Footer 등)는 export function(named export)이라서 중괄호가 필요하다.

6.2. return에 Chatbot 추가

<Footer /> 아래에 한 줄을 추가한다.

src/App.jsx (return 수정)
1
return (
2
<>
3
<Header />
4
<Outlet context={ctx} />
5
<Footer />
6
<Chatbot />
7
</>
8
);
설명
6<Chatbot /> — 플로팅 버튼과 채팅창이 모두 이 컴포넌트 안에 있으므로, 한 줄만 추가하면 된다.

7. 동작 확인

확인 항목기대 결과
화면 우하단노란색 둥근 버튼에 말풍선 아이콘 표시
버튼 클릭버튼 위에 채팅창 팝업. 아이콘이 X로 전환
채팅창 열림”안녕하세요! 영화에 대해 무엇이든 물어보세요” 메시지 표시
메시지 전송오른쪽 노란 말풍선 → ”…” 로딩 → 왼쪽 흰 말풍선
서버 에러 시”서버 연결에 실패했습니다” 에러 메시지 표시
메시지 추가자동으로 맨 아래로 스크롤
X 버튼 클릭채팅창 닫힘

정보

무료 호스팅(Render)은 첫 응답이 30초~1분 걸릴 수 있다. 서버가 슬립 상태에서 깨어나는 시간이다.


8. 모노레포 구조와 GitHub 올리기

8.1. 모노레포란?

이 프로젝트는 하나의 GitHub 저장소 안에 프론트엔드(frontend/)와 백엔드(backend/)가 함께 들어 있다. 이런 구조를 모노레포(Monorepo, 모노리포)라고 한다. 책 한 권에 소설과 삽화가 함께 들어 있는 것처럼, 하나의 저장소에서 두 프로젝트를 관리한다.

1
movie-2026/
2
├── frontend/ ← React 프론트엔드
3
│ ├── src/
4
│ ├── public/
5
│ ├── vite.config.js
6
│ ├── package.json
7
│ └── .env ← TMDB API 키 (GitHub에 올리면 안 됨)
8
└── backend/ ← Python 챗봇 서버
9
├── main.py
10
├── requirements.txt
11
└── .env ← HuggingFace API 키 (GitHub에 올리면 안 됨)

8.2. .gitignore 설정

프로젝트 최상위 폴더(movie-2026/)에 .gitignore 파일을 만들고 아래 내용을 입력한다. 비밀 정보와 불필요한 파일이 GitHub에 올라가지 않도록 막는 역할이다.

.gitignore
frontend/.env
frontend/node_modules/
frontend/dist/
backend/.env
backend/.venv/
backend/__pycache__/
설명
1프론트엔드의 TMDB API 키 파일을 제외한다.
2설치된 패키지 폴더를 제외한다. 용량이 매우 크고, npm install로 다시 설치할 수 있다.
3빌드 결과물을 제외한다. Render가 배포 시 자동으로 빌드한다.
4-6백엔드의 비밀 키, 가상환경, 캐시를 제외한다.

8.3. GitHub에 코드 올리기

  1. https://github.com 에서 **New repository(뉴 리포지토리)**를 클릭하여 새 저장소를 만든다.
  2. 프로젝트 최상위 폴더(movie-2026/)에서 터미널을 열고, 아래 명령어를 한 줄씩 실행한다.
Terminal window
1
git init
2
git add .
3
git commit -m "first commit"
4
git remote add origin https://github.com/내아이디/movie-2026.git
5
git push -u origin main
설명
1이 폴더를 Git 저장소로 초기화한다.
2모든 파일을 스테이지(stage, 올릴 준비)에 올린다.
3”first commit”이라는 메시지와 함께 기록을 남긴다.
4GitHub 원격 저장소와 연결한다. 내아이디를 본인의 GitHub 아이디로 바꾼다.
5코드를 GitHub에 올린다.

9. Render에 배포하기

Render(렌더)는 GitHub 저장소를 연결하면 코드를 자동으로 배포해 주는 무료 호스팅 서비스이다. 모노레포에서는 같은 저장소를 두 번 연결하되, 각각 다른 Root Directory(루트 디렉토리, 시작 폴더)를 지정한다.

9.1. 백엔드 배포 (Python 서버)

  1. https://render.com 에 접속하여 GitHub 계정으로 로그인한다.
  2. NewWeb Service를 클릭한다.
  3. GitHub 저장소(movie-2026)를 연결하고 아래와 같이 설정한다.
항목입력값
Namegoflex-backend (원하는 이름)
Root Directorybackend
RuntimePython 3
Build Commandpip install -r requirements.txt
Start Commanduvicorn main:app --host 0.0.0.0 --port 10000
  1. Environment(환경변수) 탭에서 아래 값을 추가한다.
KeyValue
HF_TOKENHuggingFace에서 발급받은 API 키
  1. Create Web Service를 클릭하면 배포가 시작된다.
  2. 배포 완료 후 https://goflex-backend-xxxx.onrender.com 형태의 주소가 생성된다. 이 주소를 메모해 둔다.

주의

Start Command를 반드시 uvicorn main:app --host 0.0.0.0 --port 10000으로 직접 입력해야 한다. Render는 Python 프로젝트를 감지하면 자동으로 gunicorn을 사용하려고 하는데, FastAPI는 uvicorn을 사용하므로 에러가 발생한다.

9.2. Chatbot.jsx의 BACKEND 주소 변경

배포된 백엔드 주소를 Chatbot.jsx에 반영한다. src/components/Chatbot.jsx 파일을 열고 5번 줄을 수정한다.

src/components/Chatbot.jsx
1
const BACKEND = "https://goflex-backend-xxxx.onrender.com/chat";

xxxx 부분을 9.1에서 생성된 실제 주소로 바꾼다. 수정 후 GitHub에 다시 push 한다.

Terminal window
1
git add .
2
git commit -m "update backend url"
3
git push

9.3. 프론트엔드 배포 (React)

  1. Render에서 NewStatic Site(스태틱 사이트)를 클릭한다.
  2. 같은 GitHub 저장소(movie-2026)를 다시 연결하고 아래와 같이 설정한다.
항목입력값
Namegoflex-frontend (원하는 이름)
Root Directoryfrontend
Build Commandnpm install && npm run build
Publish Directorydist
  1. Environment(환경변수) 탭에서 아래 값을 추가한다.
KeyValue
VITE_TMDB_API_KEYTMDB에서 발급받은 API 키
  1. Create Static Site를 클릭한다.
  2. 배포 완료 후 생성된 주소(예: https://goflex-frontend-xxxx.onrender.com)로 접속하면 완성된 GOFLEX를 볼 수 있다.

Render는 기본적으로 저장소의 최상위 폴더를 빌드한다. 모노레포에서는 frontend/backend/가 분리되어 있으므로, Root Directory에 어떤 폴더를 지정하느냐에 따라 전혀 다른 프로젝트가 배포된다.

서비스Root Directory빌드 대상
백엔드 (Web Service)backendPython 서버
프론트엔드 (Static Site)frontendReact 앱

같은 저장소, 같은 커밋이지만 시작 폴더만 다르게 설정하면 각각 올바른 프로젝트가 배포된다.

9.4. SPA 리다이렉트 설정

React Router를 사용하는 SPA(싱글 페이지 애플리케이션)는 한 가지 추가 설정이 필요하다. frontend/public/ 폴더에 _redirects 파일을 만들고 아래 한 줄을 입력한다.

frontend/public/_redirects
/* /index.html 200
설명
모든 URL 요청(/*)을 index.html로 보내고 200 상태코드를 반환한다. 이 설정이 없으면 /movie/550 같은 URL에 직접 접속하거나 새로고침할 때 404 에러가 발생한다. React Router가 URL을 처리하려면 항상 index.html이 먼저 로드되어야 하기 때문이다.

10. 자주 발생하는 에러

.env 파일의 API 키를 확인한다. 로컬 개발 시에는 Ctrl+Cnpm run dev로 재시작해야 반영된다. Render 배포 시에는 Environment 탭에서 VITE_TMDB_API_KEY를 정확히 입력했는지 확인한다.

Render가 Python 프로젝트를 Django로 인식하여 gunicorn을 실행하려 할 때 발생한다. Settings → Start Command를 uvicorn main:app --host 0.0.0.0 --port 10000으로 직접 입력하고 저장한 뒤 Manual Deploy를 클릭한다.

모든 영화에 예고편이 있는 것은 아니다. trailerundefined이면 “예고편 보기” 버튼 자체가 표시되지 않는다. 인기 영화 대부분은 예고편이 있으므로 상위 랭킹 영화로 테스트한다.

frontend/public/_redirects 파일이 없거나 내용이 잘못된 경우이다. 9.4 항목의 SPA 리다이렉트 설정을 확인한다.


11. 전체 코드