dev #874
3
.gitignore
vendored
3
.gitignore
vendored
@ -57,11 +57,12 @@ sl
|
||||
graphify-out/
|
||||
|
||||
# [local-only 2026-05-08] 모든 markdown 문서는 git 추적 제외 (로컬 전용)
|
||||
# 단, 루트 README.md / CLAUDE.md / AGENTS.md, .agents 및 .claude 하위의 md 는 추적 유지
|
||||
# 단, 루트 README.md / CLAUDE.md / AGENTS.md, docs, .agents 및 .claude 하위의 md 는 추적 유지
|
||||
*.md
|
||||
!/README.md
|
||||
!/CLAUDE.md
|
||||
!/AGENTS.md
|
||||
!/docs/**/*.md
|
||||
!.agents/**/*.md
|
||||
!.claude/**/*.md
|
||||
|
||||
|
||||
166
docs/PDF Gemini 평면도 분석 기능 구현 계획.md
Normal file
166
docs/PDF Gemini 평면도 분석 기능 구현 계획.md
Normal file
@ -0,0 +1,166 @@
|
||||
# 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) | Douglas–Peucker 단순화 또는 거부 |
|
||||
| 업로드 파일 크기 초과 | 클라이언트/서버 양쪽 가드 |
|
||||
| 네트워크 실패 | 모달에 재시도 버튼 + 파일 폐기 보장 |
|
||||
|
||||
---
|
||||
|
||||
## 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. `menusState` 에 `pdf-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. 의미 단위로 커밋
|
||||
Loading…
x
Reference in New Issue
Block a user