dev #872

Merged
ysCha merged 111 commits from dev into dev-deploy 2026-05-29 16:09:40 +09:00
2 changed files with 168 additions and 1 deletions
Showing only changes of commit c1247a95db - Show all commits

3
.gitignore vendored
View File

@ -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

View 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) | 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. `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. 의미 단위로 커밋