qcast-front/docs/PDF Gemini 평면도 분석 기능 구현 계획.md
2026-05-19 16:20:48 +09:00

7.1 KiB
Raw Permalink Blame History

PDF 평면도 → Gemini 분석 → 캔버스 반영 구현 계획

작성일: 2026-05-18 대상 브랜치: claude/lucid-shaw-91e51f (dev f32f9187 분기) 모델: gemini-3.1-pro-preview · 좌표 단위: mm · 진입 위치: floor-plan 상단 툴바


1. 기능 요구 사항

  1. 기본 프로세스 상에서 특정 버튼 클릭으로 사용자가 PDF 문서를 업로드한다.
  2. Gemini API 로 해당 문서의 내용 중 평면도를 분석한다.
  3. 분석 내용을 응답으로 전달받고 현재 프로세스(캔버스)로 전달한다.
  4. 분석이 성공적으로 마무리되면 업로드한 파일은 폐기 처리한다.

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, MIME application/pdf)
    • unitHint (옵션): mm | auto

5-2. 처리 단계

  1. formData.get('file') 로 PDF 수신 → 사이즈/MIME 검증
  2. GoogleGenerativeAI(GEMINI_API_KEY) 클라이언트 생성
  3. Gemini File API 업로드fileUri 획득
  4. 모델 호출 (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 강제
  5. JSON 파싱 → 좌표 검증
    • vertex ≥ 3
    • 자기교차 검출 (turf 활용)
    • 0 면적 거부
  6. 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)
  • 기존 외곽선 분기:
    • 존재 시 useSwal confirm: "기존 외곽선을 덮어쓰시겠습니까?"
    • 빈 캔버스 시 즉시 적용
  • 적용:
    • 점들을 name === 'outerLine' QLine 객체들로 생성하여 캔버스에 add
    • setOuterLinePoints(points) (recoil outerLinePointsState)
    • addPolygonByLines(lines) 흐름으로 QPolygon 생성 (기존 useRoofShapeSetting 자산 재사용)
    • canvas.renderAll()

7. 검증 / 엣지 케이스

케이스 대응
PDF 가 아닌 파일 라우트에서 MIME 거부
평면도가 아닌 PDF 모델이 빈 outerline 반환 → 친화적 메시지
좌표 ≤ 2개 / 자기교차 / 0면적 라우트에서 거부 (SizeSetting 가드와 일관)
모델 JSON 파싱 실패 retry 1회 후 실패 메시지
Gemini 호출 실패 finally 에서 File API 삭제 보장
캔버스 기존 외곽선 존재 confirm 후 덮어쓰기
응답이 거대한 폴리곤(>500 vertex) DouglasPeucker 단순화 또는 거부
업로드 파일 크기 초과 클라이언트/서버 양쪽 가드
네트워크 실패 모달에 재시도 버튼 + 파일 폐기 보장

8. 컨벤션 / 운영

  • Prettier: single quote, no semicolons, tabWidth 2, printWidth 150
  • 메시지/모달: useSwal + useMessage (raw alert() 금지)
  • 다국어: ja/ko 양쪽 키 추가 (pdf.import.*)
  • 경로 alias: @/... 우선
  • 로깅: src/util/logger.js 만 사용

커밋 단위

  1. [xxxx] feat(api) — Gemini 평면도 분석 라우트 신설
  2. [xxxx] feat(floor-plan) — PDF 임포트 모달/툴바 진입
  3. [xxxx] feat(floor-plan) — 분석 결과 외곽선 캔버스 반영
  4. [xxxx] chore — 다국어 메시지/lint 정리

마지막에 lint/type/build subagent 점검.


9. 작업 순서 (To-Do)

  1. @google/generative-ai 의존성 추가, GEMINI_API_KEY/GEMINI_MODEL env·config 매핑
  2. 서버 라우트 src/app/api/gemini/floor-plan/route.js 작성 (Gemini 호출 + 파일 폐기)
  3. menusStatepdf-import 항목 + PDF 아이콘 SVG 추가
  4. CanvasMenu.jsx onClickNav switch 에 케이스 추가, modalAtom 으로 모달 오픈
  5. PdfImportModal.jsx 작성 (idle→uploading→analyzing→success/error 상태머신)
  6. usePdfImport.js 작성 (단위/방향 정규화, 외곽선 덮어쓰기 분기, QLine/QPolygon 반영)
  7. 다국어 메시지 키 추가 (ja/ko)
  8. yarn lint + dev에서 실제 PDF 한두 건 manual test
  9. 의미 단위로 커밋