Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- DP
- OpenAI
- frontend
- 스택/큐
- React
- BFS
- modbus
- 완전탐색
- backend
- 알고리즘
- javascirpt
- VectoreStore
- retriever
- 코딩테스트
- monorepo
- queue
- turbo
- dfs
- heapq
- 프로그래머스
- LLM
- typescript
- rag
- 파이썬
- AI
- Algorithm
- python
- Two Pointer
- InfluxDB
- javascript
Archives
- Today
- Total
DM Log
[AI 프로젝트 #1] PDF Q&A #3: React + Vite 프론트엔드 UI 구축 본문
서론
- React + Vite 프론트엔드 UI를 구축하여 실제로 PDF를 업로드하고 AI에게 질문을 던져 응답을 확인할 수 있는 화면 설계
프론트엔드 디렉토리 구조
frontend/
├── apps/
│ └── pdf/ # PDF Q&A 프론트엔드
│ ├── src/
│ │ ├── components/
│ │ │ ├── UploadForm.tsx
│ │ │ └── ChatBox.tsx
│ │ ├── api/pdfApi.ts
│ │ └── main.tsx
│ ├── package.json
│ └── tsconfig.json
├── packages/
│ ├── ui/
│ ├── styles/
│ └── utils/
├── turbo.json
└── package.json
패키지 설치
- axios와 emotion 라이브러리를 공용으로 설치
# frontend 루트에서 실행
cd frontend
# axios (API 통신)
npm install axios
# emotion (전역 스타일)
npm install @emotion/react @emotion/styled
axios 유틸 설정
- /packages/utils/src/api/axiosInstance.ts
import axios from 'axios'
const api = axios.create({
baseURL: "http://localhost:5000",
headers: {
"Content-Type": "multipart/form-data",
},
});
api.interceptors.request.use((config) => {
console.log(`${config.url}로 요청 중`)
return config;
});
api.interceptors.response.use(
(res) => res,
(error) => {
console.error("요청 실패", error);
return Promise.reject(error);
}
)
export default api;
- /utils/src/index.ts
export {default as api} from "./api/axiosInstance"
PDF 업로드 UI
- /apps/pdf/src/api/pdfApi.ts
import {api} from "utils";
export async function uploadPDF(file: File) {
const formData = new FormData();
formData.append("file", file);
const {data} = await api.post("/upload", formData);
return data
}
export async function askQuestion(question: string) {
const formData = new FormData();
formData.append("question", question);
const { data } = await api.post("/ask", formData);
return data;
}
PDF 업로드 UI
- /apps/pdf/src/components/UploadForm.tsx
/** @jsxImportSource @emotion/react */
import { useState } from "react";
import { uploadPDF } from "../api/pdfApi";
const UploadForm = ({ onUpload }: { onUpload: (filename: string) => void }) => {
const [file, setFile] = useState<File | null>(null);
const [loading, setLoading] = useState(false);
const handleUpload = async () => {
if (!file) return alert("PDF 파일을 선택하세요.");
setLoading(true);
try {
const res = await uploadPDF(file);
onUpload(res.filename);
alert("파일 업로드 완료!");
} catch (err) {
console.error(err);
alert("업로드 실패");
} finally {
setLoading(false);
}
};
return (
<div>
<input type="file" accept="application/pdf" onChange={(e) => setFile(e.target.files?.[0] || null)} />
<button onClick={handleUpload} disabled={loading}>
{loading ? "업로드 중..." : "업로드"}
</button>
</div>
);
};
export default UploadForm;
질문 입력 & 답변 표시 UI
- /apps/pdf/src/components/ChatBox.tsx
/** @jsxImportSource @emotion/react */
import { useState } from "react";
import { askQuestion } from "../api/pdfApi";
const ChatBox = () => {
const [question, setQuestion] = useState("");
const [answer, setAnswer] = useState("");
const handleAsk = async () => {
if (!question) return;
try {
const res = await askQuestion(question);
setAnswer(res.answer);
} catch (err) {
console.error(err);
alert("질문 처리 중 오류 발생");
}
};
return (
<div>
<textarea
placeholder="질문을 입력하세요..."
value={question}
onChange={(e) => setQuestion(e.target.value)}
rows={3}
style={{ width: "100%", marginBottom: "8px" }}
/>
<button onClick={handleAsk}>질문하기</button>
{answer && (
<div style={{ marginTop: "16px", whiteSpace: "pre-wrap" }}>
<strong>답변:</strong> {answer}
</div>
)}
</div>
);
};
export default ChatBox;
전체 페이지 구성
- /apps/pdf/src/main.tsx
/** @jsxImportSource @emotion/react */
import React, { useState } from "react";
import ReactDOM from "react-dom/client";
import UploadForm from "./components/UploadForm";
import ChatBox from "./components/ChatBox";
import { GlobalStyle } from "styles";
const App = () => {
const [uploadedFile, setUploadedFile] = useState("");
return (
<>
<GlobalStyle />
<div style={{ padding: "24px" }}>
<h1>📄 PDF Q&A</h1>
<UploadForm onUpload={setUploadedFile} />
{uploadedFile && (
<>
<hr style={{ margin: "24px 0" }} />
<ChatBox />
</>
)}
</div>
</>
);
};
ReactDOM.createRoot(document.getElementById("root")!).render(<App />);
실행
cd frontend
npm install
npx turbo run dev --filter=pdf
'PJT > AI PJT' 카테고리의 다른 글
[AI 프로젝트 #1] PDF Q&A #2: LangChain + MCP / FastAPI 기반 백엔드 서버 구현 (OpenAPI) (0) | 2025.10.19 |
---|---|
[AI 프로젝트 #1] PDF Q&A #1: 프로젝트 개요와 서비스 구조 (0) | 2025.10.15 |
[AI 프로젝트 #0] 초기 세팅 #4: 공용 패키지(UI, 스타일, 유틸) 구조 설계 (0) | 2025.10.13 |
[AI 프로젝트 #0] 초기 세팅 #3: Turborepo 실행 환경 구성 방법 (0) | 2025.10.13 |
[AI 프로젝트 #0] 초기 세팅 #2: Node, Python, Vite 기반 모노레포 환경 구축 및 실행 (0) | 2025.09.27 |