[Jobby] 챗 UI 1
오늘 목표
4편까지는:
- 서버에서 Dialogflow로 텍스트 질문을 보내는
textQuery함수를 만들고 POST /api/dialogflow/textQuery라우트를 추가해서- Postman 같은 도구로 API를 직접 호출해, 응답 JSON까지 확인했다.
5편에서는 드디어 React에서 이 API를 직접 호출해서:
- 화면에 간단한 입력창/버튼을 만들고
- 사용자가 입력한 문장을
POST /api/dialogflow/textQuery로 보내서 - Dialogflow가 돌려준
fulfillmentText를 화면에 한 줄이라도 띄워 본다.
이 편이 끝나면, 브라우저에서 “입력칸에 질문 → 조비가 한 줄이라도 대답”하는 기본 챗 UI를 확인할 수 있다.
1. Jobby 구조 다시 짚고 가기
지금까지 만든 Jobby 구조를, 프론트 기준으로 다시 한 번 정리하면:
- 사용자가 React 화면에서 질문을 입력하고 버튼을 누른다.
- React가 이 텍스트를
POST /api/dialogflow/textQuery로 보낸다. (실제 주소는.env에서 관리하는VITE_API_URL기준) - 서버(Express)가 이 텍스트를 Dialogflow
detectIntent로 넘긴다. - Dialogflow가 응답을 계산해서 서버에 돌려준다.
- 서버가 응답을 JSON으로 가공해 React로 보내고, React가 그걸 화면에 보여준다.
4편까지는 3~5번을 Postman으로만 확인했는데,
5편에서는 1~2번을 React에서 구현해서 전체 흐름을 한 번에 이어 본다.
2. React 쪽에 기본 Chat 컴포넌트 만들기
처음에는 Vite 프로젝트가 client/vite-project/src처럼 한 단계 더 안쪽에 생성되었는데, 나중에 서버(server/)와 구조를 맞추고 싶어서 vite-project 폴더를 제거하고, 그 안에 있던 package.json, index.html, src 폴더를 전부 client/ 바로 아래로 끌어올렸다. 지금은 client/src/Chat.jsx처럼 더 단순한 경로를 쓰는 구조로 정리된 상태다.
먼저, client 쪽에 “입력창 + 버튼 + 답변 한 줄”만 있는 아주 단순한 컴포넌트를 만든다.
1
2
3
cd client
# Vite 기준: src 밑에 Chat.jsx 같은 파일을 하나 만든다.
touch src/Chat.jsx
그리고 API 주소를 환경변수로 빼기 위해 client/.env 파일을 하나 만들어서 다음을 넣어 둔다.
VITE_API_URL=http://localhost:5000
이제 src/Chat.jsx 안에 다음과 같이 작성한다:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// client/src/Chat.jsx
import { useState } from "react";
const API_URL = import.meta.env.VITE_API_URL;
function Chat() {
const [input, setInput] = useState("");
const [answer, setAnswer] = useState("");
const handleSubmit = async (e) => {
e.preventDefault();
if (!input.trim()) return;
try {
const response = await fetch(`${API_URL}/api/dialogflow/textQuery`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ text: input }),
});
const data = await response.json();
if (response.ok) {
setAnswer(data.fulfillmentText || "(응답이 없어요)");
} else {
setAnswer(`에러: ${data.error || "알 수 없는 에러"}`);
}
} catch (error) {
console.error("요청 중 에러:", error);
setAnswer("요청 중 에러가 발생했습니다.");
}
};
return (
<div style=>
<h1>Jobby Chat</h1>
<form onSubmit={handleSubmit} style=>
<input
type="text"
placeholder="질문을 입력해 주세요"
value={input}
onChange={(e) => setInput(e.target.value)}
style=
/>
<button type="submit">보내기</button>
</form>
<div style=>
<h2>조비의 대답</h2>
<p>{answer}</p>
</div>
</div>
);
}
export default Chat;
설명:
input: 사용자가 입력한 질문 텍스트 상태.answer: 서버/조비가 돌려준 한 줄 답변 상태.API_URL: Vite 환경변수(VITE_API_URL)에서 가져온 서버 주소로, 나중에 배포할 때.env만 바꿔서 주소를 바꿀 수 있다.handleSubmit에서fetch로POST /api/dialogflow/textQuery를 호출하고, 응답의fulfillmentText를answer에 넣는다.
3. Chat 컴포넌트를 실제 화면에 붙이기
Vite 기본 템플릿 기준으로, src/main.jsx 또는 src/App.jsx에서 Chat 컴포넌트를 불러온다.
예를 들어 src/App.jsx를 이렇게 바꾼다:
1
2
3
4
5
6
7
8
9
// client/src/App.jsx
import Chat from "./Chat";
function App() {
return <Chat />;
}
export default App;
그리고 src/main.jsx는 Vite 기본 설정 그대로 두고, <App />만 렌더링하면 된다.
이렇게 하면 브라우저에서 npm run dev로 프론트를 띄웠을 때,
곧바로 Chat 화면이 보이고, 입력창에 질문을 적고 “보내기”를 누르면 조비의 한 줄 대답이 아래에 나타난다.
4. 서버/클라이언트 함께 띄우기
이제 실제로 동작을 확인해 본다.
- 서버:
1
2
3
4
5
cd server
npm install cors
node index.js
# 또는
npm run start
server/index.js에는 CORS 해결을 위해 다음 설정을 추가해 두었다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// server/index.js
const express = require("express");
const cors = require("cors");
const app = express();
const PORT = 5000;
// 개발 환경에서는 localhost:5173에서 오는 요청만 허용
app.use(
cors({
origin: "http://localhost:5173",
})
);
app.use(express.json());
// 이하 라우트들...
이 설정 덕분에 브라우저에서
Access to fetch at ... from origin ... has been blocked by CORS policyNo 'Access-Control-Allow-Origin' header is present on the requested resource.
같은 CORS 에러 없이 React(5173)에서 Express(5000)로 요청을 보낼 수 있게 된다.
- 클라이언트(다른 터미널):
1
2
cd client
npm run dev
브라우저에서 http://localhost:5173 (또는 Vite가 알려주는 주소)에 접속.
- Chat 화면에서:
- 입력창에 [translate:자기소개 해줘]를 적고 보내기
- 아래에 “안녕하세요, 취업용 자기소개 챗봇 Jobby입니다…” 같이
Dialogflow에 설정한fulfillmentText가 뜨면 성공.
(부록) CORS 에러와 해결 과정 메모
처음에 React에서 바로 http://localhost:5000/api/dialogflow/textQuery로 요청을 보내자, 브라우저 콘솔에 이런 에러가 찍혔다.
Access to fetch at 'http://localhost:5000/api/dialogflow/textQuery' from origin 'http://localhost:5173' has been blocked by CORS policyNo 'Access-Control-Allow-Origin' header is present on the requested resource.
React 개발 서버(5173)와 Express 서버(5000)가 서로 다른 포트라, 브라우저가 “서로 다른 출처(origin)”라고 판단하고 보안을 이유로 요청을 막은 것이다.
이를 해결하기 위해 Express 서버에 cors 미들웨어를 설치하고, 개발 단계에서는 origin: "http://localhost:5173"를 허용하도록 설정했다. 이렇게 하면 서버 응답에 Access-Control-Allow-Origin 헤더가 추가되고, 브라우저가 React → Express 요청을 정상적으로 통과시킨다.
** 사실 임시로 전체 허용으로 해둠. 아직 로컬환경에서 개발중이라 전체 허용으로 해두고 마지막에 수정할 예정…!
오늘 실제로 한 작업 (로그)
client/src/Chat.jsx생성input/answer상태를 만들고import.meta.env.VITE_API_URL에서 읽은 서버 주소를 사용해${API_URL}/api/dialogflow/textQuery로 질문을 보내도록 구현- 응답의
fulfillmentText를 화면에 표시하는 기본 챗 UI 완성
client/.env에VITE_API_URL=http://localhost:5000추가해서, API 주소를 환경변수로 관리할 수 있도록 정리client/src/App.jsx에서<Chat />컴포넌트를 렌더링하도록 변경server/index.js에cors미들웨어를 추가하고, 개발 환경에서http://localhost:5173만 허용하도록 설정해 CORS 에러 해결- 서버(
node index.js)와 클라이언트(npm run dev)를 동시에 띄워, 브라우저에서 실제로 “질문 → 서버 → Dialogflow → 서버 → 화면” 흐름을 확인
다음 글에서 할 것
다음 편에서는:
- 질문/답변을 한 줄만 보여주는 대신, 채팅 내역이 위에서 아래로 쌓이는 형태의 UI를 만들고
- 질문 말풍선/답변 말풍선 스타일을 간단히 나눠서
- “실제 채팅앱처럼 대화가 흘러가는 느낌”을 만들 예정이다.