kintex/docs/analysis/reroomai-source.md
zio 3da6b1c092 feat(harness): ReRoomAI 분석 반영 — PLANNING v1.1 나노바나나 파이프라인 확정 + client.py 재작성 + Stitch 전달
- 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>
2026-07-11 16:40:11 +09:00

13 KiB
Raw Blame History

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 선택과 서버 프롬프트가 단일 출처 공유:

// 공간 유형: { 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/genaigemini-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 구조보존 품질은 재평가 필요.