7.1 KiB
7.1 KiB
PDF 평면도 → Gemini 분석 → 캔버스 반영 구현 계획
작성일: 2026-05-18 대상 브랜치:
claude/lucid-shaw-91e51f(devf32f9187분기) 모델:gemini-3.1-pro-preview· 좌표 단위:mm· 진입 위치: floor-plan 상단 툴바
1. 기능 요구 사항
- 기본 프로세스 상에서 특정 버튼 클릭으로 사용자가 PDF 문서를 업로드한다.
- Gemini API 로 해당 문서의 내용 중 평면도를 분석한다.
- 분석 내용을 응답으로 전달받고 현재 프로세스(캔버스)로 전달한다.
- 분석이 성공적으로 마무리되면 업로드한 파일은 폐기 처리한다.
2. 결정사항
| 항목 | 선택 |
|---|---|
| 워크트리 | 현재 워크트리(lucid-shaw-91e51f)에서 진행 |
| 캔버스 반영 형태 | 외곽선 폴리곤 좌표(JSON) |
| 버튼 위치 | floor-plan 상단 툴바 |
| API 호출 위치 | 서버 라우트 (/api/gemini/floor-plan) |
| Gemini 모델 | gemini-3.1-pro-preview |
| 좌표 단위 | mm |
| 툴바 아이콘 | 신규 PDF 아이콘 추가 |
3. 전체 흐름
[툴바 버튼] → [PdfImportModal] → POST /api/gemini/floor-plan (multipart)
↓
Gemini File API 업로드
↓
외곽선 좌표 JSON 응답
↓
Gemini 파일 삭제 (finally)
↓
응답 좌표 → 좌표 검증/정규화 → outerLinePointsState + QLine/QPolygon 생성
4. 핵심 파일 (수정/신규)
| 영역 | 경로 | 변경 |
|---|---|---|
| 서버 라우트 | src/app/api/gemini/floor-plan/route.js |
신규 — multipart 수신 + Gemini 호출 + finally 폐기 |
| 환경설정 | src/config/config.*.js |
GEMINI_API_KEY, GEMINI_MODEL 추가 |
| 의존성 | package.json |
@google/generative-ai 추가 |
| 메뉴 등록 | src/store/menuAtom.js (menusState) |
{ type: 'pdf-import', name, icon } 항목 |
| 툴바 분기 | src/components/floor-plan/CanvasMenu.jsx (onClickNav) |
case 'pdf-import' 추가 |
| 업로드 모달 | src/components/floor-plan/modal/pdfImport/PdfImportModal.jsx |
신규 — 파일 선택/진행/에러 |
| 캔버스 반영 hook | src/hooks/pdf-import/usePdfImport.js |
신규 — 좌표 검증 + 캔버스 반영 |
| 다국어 메시지 | 메시지 키 추가 | pdf.import.* (ja/ko) |
| 아이콘 | public/ 또는 기존 아이콘 디렉토리 |
PDF 임포트용 SVG 추가 |
5. 서버 라우트 설계 (/api/gemini/floor-plan)
5-1. 입력
multipart/form-data- 필드:
file: PDF (≤ 20MB, MIMEapplication/pdf)unitHint(옵션):mm|auto
5-2. 처리 단계
formData.get('file')로 PDF 수신 → 사이즈/MIME 검증GoogleGenerativeAI(GEMINI_API_KEY)클라이언트 생성- Gemini File API 업로드 →
fileUri획득 - 모델 호출 (
gemini-3.1-pro-preview)- 프롬프트 핵심:
- 역할: "건축 평면도 외곽선 추출 전문가"
- 출력: JSON only, 단위 mm
- 스키마:
{ outerline: [{x,y}, ...], unit: 'mm', scale?: number, confidence: 0..1, notes?: string } - "평면도가 아니면
{ outerline: [], notes: '...' }반환"
response_mime_type=application/json,response_schema강제
- 프롬프트 핵심:
- JSON 파싱 → 좌표 검증
- vertex ≥ 3
- 자기교차 검출 (turf 활용)
- 0 면적 거부
- finally 블록에서
genai.files.delete(uploadedFile.name)— 성공/실패 무관 삭제
5-3. 출력
- 성공:
{ outerline: [{x,y}, ...], unit: 'mm', confidence } - 실패:
{ error: { code, message } }
5-4. 보안 / 운영
GEMINI_API_KEY는 서버 env 만 사용, 클라이언트 노출 금지- S3 등 영구 저장소에 절대 저장하지 않음 (요구 4번: 폐기)
- 로깅:
src/util/logger.js만 사용, raw 응답은NEXT_PUBLIC_ENABLE_LOGGING=true일 때만 출력
6. 클라이언트 흐름
6-1. 모달 (PdfImportModal)
- 상태머신:
idle → uploading → analyzing → success | error - 호출:
fetch('/api/gemini/floor-plan', { method: 'POST', body: formData }) - 성공:
usePdfImport.applyOuterline(points, unit)호출 후 모달 닫기 - 실패:
useSwal로 에러 메시지 표시
6-2. 캔버스 반영 hook (usePdfImport)
- 단위 변환:
unit === 'mm'응답 → canvas px/mm 비율로 변환 - 정규화: 시계방향 정렬, bounding box 중앙 정렬(translate)
- 기존 외곽선 분기:
- 존재 시
useSwalconfirm: "기존 외곽선을 덮어쓰시겠습니까?" - 빈 캔버스 시 즉시 적용
- 존재 시
- 적용:
- 점들을
name === 'outerLine'QLine 객체들로 생성하여 캔버스에 add setOuterLinePoints(points)(recoilouterLinePointsState)addPolygonByLines(lines)흐름으로 QPolygon 생성 (기존useRoofShapeSetting자산 재사용)canvas.renderAll()
- 점들을
7. 검증 / 엣지 케이스
| 케이스 | 대응 |
|---|---|
| PDF 가 아닌 파일 | 라우트에서 MIME 거부 |
| 평면도가 아닌 PDF | 모델이 빈 outerline 반환 → 친화적 메시지 |
| 좌표 ≤ 2개 / 자기교차 / 0면적 | 라우트에서 거부 (SizeSetting 가드와 일관) |
| 모델 JSON 파싱 실패 | retry 1회 후 실패 메시지 |
| Gemini 호출 실패 | finally 에서 File API 삭제 보장 |
| 캔버스 기존 외곽선 존재 | confirm 후 덮어쓰기 |
| 응답이 거대한 폴리곤(>500 vertex) | Douglas–Peucker 단순화 또는 거부 |
| 업로드 파일 크기 초과 | 클라이언트/서버 양쪽 가드 |
| 네트워크 실패 | 모달에 재시도 버튼 + 파일 폐기 보장 |
8. 컨벤션 / 운영
- Prettier: single quote, no semicolons, tabWidth 2, printWidth 150
- 메시지/모달:
useSwal+useMessage(rawalert()금지) - 다국어:
ja/ko양쪽 키 추가 (pdf.import.*) - 경로 alias:
@/...우선 - 로깅:
src/util/logger.js만 사용
커밋 단위
[xxxx] feat(api) — Gemini 평면도 분석 라우트 신설[xxxx] feat(floor-plan) — PDF 임포트 모달/툴바 진입[xxxx] feat(floor-plan) — 분석 결과 외곽선 캔버스 반영[xxxx] chore — 다국어 메시지/lint 정리
마지막에 lint/type/build subagent 점검.
9. 작업 순서 (To-Do)
@google/generative-ai의존성 추가,GEMINI_API_KEY/GEMINI_MODELenv·config 매핑- 서버 라우트
src/app/api/gemini/floor-plan/route.js작성 (Gemini 호출 + 파일 폐기) menusState에pdf-import항목 + PDF 아이콘 SVG 추가CanvasMenu.jsxonClickNavswitch 에 케이스 추가,modalAtom으로 모달 오픈PdfImportModal.jsx작성 (idle→uploading→analyzing→success/error 상태머신)usePdfImport.js작성 (단위/방향 정규화, 외곽선 덮어쓰기 분기, QLine/QPolygon 반영)- 다국어 메시지 키 추가 (ja/ko)
yarn lint+ dev에서 실제 PDF 한두 건 manual test- 의미 단위로 커밋