- docs/analysis/reroomai-source.md 신규: gemini-3.1-flash-image-preview image-to-image, 보존/교체 프롬프트, CompareSlider/전처리 차용 - PLANNING v1.1: §6-4/6-5 나노바나나 호출스택·부스 프롬프트 빌더 확정, R11 해소·R12(Gemini 외부API 승인 게이트) 추가 - tools/nanobanana/client.py 전면 재작성: build_booth_prompt 4단 조립, 메타데이터 임베드, S6 래스터 합성 분리, seed 폴백, 방어 로직 - BACKLOG B-02/B-03/B-09/B-11/B-12 done - kintex.com 2차 검증 순증 사실 반영(기본부스 사양·이격·홀 스펙) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
180 lines
13 KiB
Markdown
180 lines
13 KiB
Markdown
# ReRoomAI 소스코드 분석
|
||
|
||
> 분석일: 2026-07-11 · 대상: `C:\GUARDiA\workspace\ReRoomAI` — AI 공간 인테리어 리디자인 웹 서비스
|
||
> 목적: 킨텍스 전시 부스 "시공 후 결과 사진 생성 AI" 시스템(PLANNING §6 나노바나나 파이프라인)의 구조보존 제어 파라미터 확정
|
||
> 연계: PLANNING.md R11 / BACKLOG B-11 해소 근거 문서
|
||
|
||
---
|
||
|
||
## 1. 프로젝트 개요
|
||
|
||
**ReRoom AI**는 방 사진 한 장을 업로드하고 공간 유형·인테리어 스타일을 선택하면 약 10초 만에 새로운 분위기의 방으로 재렌더링하는 AI 인테리어 리디자인 웹 서비스다.
|
||
|
||
핵심 설계 철학은 **"건축학적 뼈대 보존 + 표면 요소 교체"**. 원본 방의 벽·창문·문·천장·**카메라 시야 구도(perspective)**는 그대로 유지하고 가구·조명·색감·장식만 선택한 테마로 바꾼다. 이것이 킨텍스 부스 시스템에 이식할 가장 중요한 개념 — "빈 부스 공간 골격은 유지하고 그 안에 배치·인테리어·조명·배선만 시공된 결과를 합성"과 동일한 문제 구조다.
|
||
|
||
주요 기능:
|
||
- 6가지 공간 유형(거실/침실/주방/욕실/서재/원룸) × 8가지 스타일(모던/미니멀/북유럽/인더스트리얼/재팬디/미드센추리/한옥/호텔라운지)
|
||
- **이중 API 모드**: 데모 모드(서버 키, 브라우저 로컬 2회 + IP당 하루 10회 제한) / BYOK 모드(사용자 개인 키, 무제한)
|
||
- **Before/After 드래그 비교 슬라이더**(마우스·터치·키보드 지원)
|
||
- 결과 PNG 다운로드 + "다른 스타일로 다시 디자인"(원본 재업로드 없이 연속 실험)
|
||
- 클라이언트 Canvas 전처리(긴 쪽 1024px 다운스케일)
|
||
|
||
---
|
||
|
||
## 2. 기술 스택
|
||
|
||
| 레이어 | 기술 |
|
||
|--------|------|
|
||
| 프레임워크 | **Next.js 16.2.10** (App Router, Node runtime) — 단일 리포 서버리스, Vercel 배포 지향 |
|
||
| 언어 | TypeScript 5 |
|
||
| UI | React 19.2.4 |
|
||
| 스타일 | Tailwind CSS v4 (`@theme` 커스텀 토큰), Pretendard + Noto Serif KR (`next/font`) |
|
||
| **AI SDK** | **`@google/genai` ^2.10.0** (Google 공식 SDK) |
|
||
| **모델** | **`gemini-3.1-flash-image-preview`** (= 나노바나나 2 / Nano Banana 2) |
|
||
| 의존성 | 극히 단순 — `@google/genai`, `next`, `react`, `react-dom` **4개뿐** |
|
||
|
||
> 주목: `AGENTS.md`에 "이 Next.js는 학습 데이터와 다르다, 코드 작성 전 `node_modules/next/dist/docs/` 가이드를 읽으라"는 경고. Next 16 신버전이라 관례가 다를 수 있음.
|
||
|
||
---
|
||
|
||
## 3. 디렉터리 구조
|
||
|
||
```
|
||
ReRoomAI/
|
||
├── app/
|
||
│ ├── layout.tsx # 폰트·메타데이터·OG
|
||
│ ├── page.tsx # 섹션 조립: Header→Hero→StyleGallery→HowItWorks→Studio→Faq→Footer
|
||
│ ├── globals.css # 디자인 토큰(@theme)·그레인/리빌
|
||
│ └── api/generate/route.ts # ★ Gemini 이미지 생성 라우트(IP 레이트리밋 포함) — 시스템의 심장
|
||
├── components/
|
||
│ ├── Studio.tsx # ★ 메인 인터랙션(업로드·옵션·생성·결과) — 클라이언트 플로우 전체
|
||
│ ├── CompareSlider.tsx # ★ Before/After 슬라이더(clip-path + 포인터캡처)
|
||
│ ├── Header/Hero/StyleGallery/StyleCards/HowItWorks/Faq/Footer.tsx # 랜딩 섹션
|
||
│ └── Reveal.tsx # 스크롤 리빌 래퍼
|
||
├── lib/
|
||
│ ├── constants.ts # ★ 공간유형·스타일 정의 (UI와 서버 프롬프트 단일 출처)
|
||
│ └── useLocalStorage.ts # SSR 안전 localStorage 훅
|
||
├── .env.example # GEMINI_API_KEY
|
||
└── package.json
|
||
```
|
||
|
||
핵심 파일 4개: `app/api/generate/route.ts`(백엔드 생성), `lib/constants.ts`(프롬프트 사전), `components/Studio.tsx`(클라이언트 플로우), `components/CompareSlider.tsx`(결과 비교 UI).
|
||
|
||
---
|
||
|
||
## 4. AI / 이미지 생성 파이프라인 ★
|
||
|
||
### 사용 모델·API
|
||
- **Google Gemini `gemini-3.1-flash-image-preview`(나노바나나 2)** 를 `@google/genai` 공식 SDK의 `ai.models.generateContent()`로 호출.
|
||
- **입력 이미지 + 텍스트 지시문을 함께 전달**하는 멀티모달 image-to-image(인페인팅형 편집). Stable Diffusion·ControlNet 등 미사용 — 순수 Gemini 이미지 모델 단일 호출로 원본 구조를 참조·보존.
|
||
|
||
### 이미지 흐름 (입력 → 변환 → 결과)
|
||
|
||
```
|
||
[클라이언트 Studio.tsx]
|
||
1. 파일 업로드(드래그앤드롭/클릭) — image/* 검증, 10MB 상한
|
||
2. Canvas 전처리: 긴 쪽 1024px 다운스케일 → canvas.toDataURL('image/jpeg', 0.85) → base64 data URL (전송량·비용 절감 핵심)
|
||
3. POST /api/generate { image(base64), roomTypeId, styleId, byokKey }
|
||
|
||
[서버 route.ts — runtime='nodejs', maxDuration=60]
|
||
4. content-length 8MB 가드 → JSON 파싱 → roomType/style 유효성 검증
|
||
5. API 키 결정: BYOK 우선, 없으면 process.env.GEMINI_API_KEY
|
||
6. 데모 모드면 IP당 일일 제한(Map 인메모리) 체크
|
||
7. data URL에서 mimeType + base64 분리(정규식), 8MB*1.33 재검증
|
||
8. 프롬프트 조립 → Gemini generateContent 호출
|
||
9. 응답 candidate에서 inlineData(생성 이미지 base64) 추출
|
||
- finishReason==='SAFETY' → 차단 에러
|
||
- 성공 시에만 IP 카운트 차감(실패는 횟수 소모 안 함)
|
||
10. { image: base64 } 반환
|
||
|
||
[클라이언트]
|
||
11. `data:image/png;base64,${data.image}`로 resultImage 세팅
|
||
12. CompareSlider에 before(업로드)·after(결과) 주입
|
||
```
|
||
|
||
### 프롬프트 구성 방식 (★ 가장 차용 가치 높음)
|
||
|
||
**구조화된 사전(dictionary) 조각을 템플릿에 조립**. `lib/constants.ts`가 UI 라벨(한글)과 프롬프트 조각(영문)을 한 객체에 묶어 **UI 선택과 서버 프롬프트가 단일 출처 공유**:
|
||
|
||
```ts
|
||
// 공간 유형: { id, label(한글), prompt(영문) }
|
||
{ id: "living_room", label: "거실", prompt: "living room" }
|
||
|
||
// 스타일: { id, label, desc, swatch(색3종), prompt(상세 지시문) }
|
||
{ id: "modern", label: "모던", swatch:["#2b2b2e",...],
|
||
prompt: "sleek modern style: clean lines, neutral palette with charcoal and greige,
|
||
low-profile furniture, matte finishes, statement lighting" }
|
||
```
|
||
|
||
최종 지시문 템플릿(route.ts:106):
|
||
```
|
||
Redesign this {roomType.prompt} interior in {style.prompt}.
|
||
Keep the room architecture — walls, windows, doors, ceiling and camera perspective — exactly the same.
|
||
Replace furniture, lighting, color palette and decor to match the target style.
|
||
Photorealistic interior photography, natural lighting, high detail.
|
||
```
|
||
|
||
프롬프트 4단 구성: **① 대상+스타일 지정 → ② "보존할 것" 명시적 잠금(architecture/perspective) → ③ "교체할 것" 명시 → ④ 사진 품질 지시(photorealistic/natural lighting/high detail)**. 이 "보존/교체 명시적 분리" 패턴이 부스 시스템의 핵심.
|
||
|
||
---
|
||
|
||
## 5. 킨텍스 부스 시스템 차용 패턴 ★★★
|
||
|
||
(전시 부스 배치·인테리어·전기·조명·네트워크 배선 자동화 → 나노바나나 시공 후 결과 사진)
|
||
|
||
**(A) 모델·SDK 선택 — 검증된 정답 그대로 채택**
|
||
- `@google/genai` + `gemini-3.1-flash-image-preview`, `generateContent`에 `{ inlineData:{mimeType,data} }`(입력 이미지) + `{ text: instruction }`(지시문)을 `parts` 배열로 전달하는 호출 형태 복제. `nanobanana-visualize` 스킬이 이 호출 패턴을 표준화.
|
||
|
||
**(B) "보존/교체 명시적 분리" 프롬프트 아키텍처 — 부스 도메인 치환**
|
||
- ReRoom "walls·windows·ceiling·camera perspective exactly same" → 부스 **"부스 외곽 치수·기둥·바닥 트렌치 그리드·천장 트러스·통로 방향·카메라 앵글 유지"**.
|
||
- ReRoom "furniture·lighting·color·decor 교체" → 부스 **"집기(데스크·선반·배너·사이니지)·조명 기구·전기 콘센트·네트워크 AP·카펫/부스 벽면 그래픽 배치"**.
|
||
- **구조화 사전 재사용**: `ROOM_TYPES`/`STYLES` → 부스 도메인 사전
|
||
- `BOOTH_TYPES`(독립/조립/코너/아일랜드, 3×3·6×3…)
|
||
- `BOOTH_STYLES`(럭셔리/테크/친환경/미니멀…)
|
||
- `FIXTURE_LAYERS`(조명 레이어·전기 배선 레이어·네트워크 배선 레이어) — **레이어별 프롬프트 조각** 조립로 "조명만 야간 연출"(S2)·"배선 오버레이"(S6) 변형 렌더.
|
||
- 각 객체가 `{ id, label(한글), prompt(영문), swatch }`를 갖고 **UI 선택 ↔ 서버 프롬프트 단일 출처 공유** 유지 시 유지보수 비용 급감.
|
||
|
||
**(C) Before/After 비교 슬라이더(`CompareSlider.tsx`) — 거의 무수정 재사용**
|
||
- `clip-path: inset(0 {100-pos}% 0 0)`로 After 레이어 클립 → 리사이즈에도 픽셀 정렬 유지. 포인터 캡처 + 키보드(방향키·Shift 큰 스텝) + ARIA slider 접근성 완비 자립 컴포넌트. design.md S5(시공 전 빈 부스 ↔ 시공 후 렌더)에 그대로 투입. React/Next 스택이면 파일 복사 수준.
|
||
|
||
**(D) 클라이언트 Canvas 전처리(`Studio.handleImageFile`)**
|
||
- 긴 쪽 1024px 다운스케일 + JPEG 0.85 → base64. 전송량·모델 비용·응답시간 동시 절감 필수 전처리. 부스 도면/현장 사진 동일 적용.
|
||
|
||
**(E) API 라우트 방어 로직 — 프로덕션 안정성 템플릿**
|
||
- 다층 크기 가드(content-length 8MB → base64 ×1.33 재검증), `finishReason==='SAFETY'` 처리, **에러 분기**(API_KEY_INVALID / RESOURCE_EXHAUSTED·quota·429 / SAFETY)로 친화적 한글 메시지. **성공 시에만 사용량 차감**. 부스 API(RenderJob 워커)의 골격으로 복제.
|
||
|
||
**(F) 이중 키 모드(데모/BYOK) + IP 레이트리밋**
|
||
- 서버 인메모리 `Map<ip,{count,resetAt}>` 24h 윈도우. 전시회 현장 태블릿 데모 노출에 유용. **인메모리라 재시작·다중 인스턴스 취약** → 우리 시스템은 PostgreSQL/Redis 기반 RenderJob 쿼터로 대체(PLANNING §6-4 비용 제어와 통합).
|
||
|
||
**(G) 단계별 로딩 UX**
|
||
- `LOADING_STATUSES`("공간 구조 분석 → 스타일 요소 배치 → 조명·색상 튜닝 → 최종 고화질 렌더링") 2.5초 순환. 부스용 "부스 골격 인식 → 집기 배치 → 전기·조명 배선 → 최종 렌더링"으로 치환. design.md 진행 배지("생성 중… 평균 40초")와 연결.
|
||
|
||
---
|
||
|
||
## 6. UI / 화면 구성
|
||
|
||
단일 페이지(랜딩) 서비스. `page.tsx` 세로 조립: Header → Hero(CompareSlider LCP priority) → StyleGallery(카드 클릭 시 `window` 커스텀이벤트 `reroom:style`로 Studio 전달) → HowItWorks → **Studio ★** → Faq → Footer.
|
||
|
||
**Studio 흐름**(단일 컴포넌트가 입력→로딩→결과 3상태 조건부 렌더):
|
||
1. **입력** — 좌: `01 원본 업로드`(드래그존+미리보기), 우: `02 공간 유형`(pill), `03 스타일`(스와치 카드). 하단: BYOK 토글+키, 에러 alert, "생성하기".
|
||
2. **로딩** — 바운스 도트 + 순환 상태 텍스트(`aria-live`) + "약 10초".
|
||
3. **결과** — "Redesign Complete" 배지 + CompareSlider + [PNG 다운로드]/[다른 스타일]/[다른 사진].
|
||
|
||
상태 전부 로컬 `useState` + `useLocalStorage`(무료횟수·BYOK·키 영속). 전역 스토어·라우팅 없음 — 극경량 단일화면 SPA. 부스 초기 PoC도 이 패턴으로 빠르게 구현 후 확장 가능.
|
||
|
||
---
|
||
|
||
## 7. 설계 착수 3대 차용 결론
|
||
|
||
1. **호출 스택 그대로**: `@google/genai` → `gemini-3.1-flash-image-preview`, 입력이미지(inlineData)+지시문(text) parts 전달 image-to-image 편집. `route.ts`가 프로덕션 API 골격 템플릿.
|
||
2. **프롬프트 = 구조화 사전 조립 + 보존/교체 명시 분리**: `constants.ts` `{id,label,prompt}`를 부스 도메인(부스타입·스타일·조명/전기/네트워크 레이어)으로 치환, "골격·앵글 유지 / 집기·배선·조명 배치" 명시 잠금 템플릿.
|
||
3. **CompareSlider·Canvas 전처리·단계별 로딩 UX** 파일 복사 수준 재사용(React/Next 전제).
|
||
|
||
---
|
||
|
||
## 8. ★ 보안 결정 필요 사항 (선행)
|
||
|
||
나노바나나(Gemini) 이미지 생성은 **외부 API 호출**이다. GUARDiA 보안 제약(루트 CLAUDE.md)상 외부 API는 원칙 금지이며 현재 승인된 예외는 `api.anthropic.com`뿐 — **Gemini(generativelanguage.googleapis.com)는 미승인**. 단, 본 kintex 프로젝트는 GUARDiA ITSM(관공서 인프라 관제)과 별개 도메인의 독립 저장소(`zio/kintex`)이며, PLANNING v1.0과 사용자 요구가 명시적으로 나노바나나를 채택하고 있다.
|
||
|
||
**조치**: 부스 M5 파이프라인 구현 착수 전 소유자에게 **Gemini 외부 호출 승인 여부**를 확정한다. 승인 시 `GEMINI_API_KEY`를 서버 env로만 로드(코드·DB·커밋·로그·응답 기록 금지, ReRoomAI (E) 방어 패턴 준수). 미승인 시 대안 — 온프레미스 이미지 생성(SDXL 등) 어댑터로 폴백하되 image-to-image 구조보존 품질은 재평가 필요.
|