REPO DEEP DIVE · 2026-07-29 · ABI/SCREENSHOT-TO-CODE · 스크린샷 한 장을 동작하는 코드로

abi/screenshot-to-code 딥다이브
"그림을 보여주면 코드가 나온다"는 말의 실제 안쪽

screenshot-to-code는 스크린샷·목업(mockup)·피그마(Figma) 디자인 이미지, 화면 녹화 영상, 또는 짧은 텍스트 설명을 받아 실제로 브라우저에서 동작하는 프론트엔드 코드(HTML/React/Vue/Bootstrap/Ionic)로 바꿔주는 오픈소스 프로젝트다. 겉보기엔 "이미지 → 코드 변환기" 한 문장으로 요약되지만, 소스코드 안쪽에는 WebSocket 스트리밍, 도구 호출(tool-calling) 에이전트 루프, 자체 스크린샷 검증, 실제 로고·이미지 추출, 평가(evals) 기반 모델 큐레이션 같은 제법 두꺼운 엔지니어링이 들어 있다.

이 문서는 README만 보고 쓴 요약이 아니라, 저장소를 직접 클론해 backend/(FastAPI + Python)와 frontend/(React + Vite)의 실제 소스코드를 파일 단위로 읽고 정리한 것이다. 별점 수 같은 인기 지표는 다루지 않는다 — 코드가 실제로 어떻게 짜여 있는지에만 집중한다.

(저장소 abi/screenshot-to-code · 제작 Abi Raja · 라이선스 MIT · 최신 커밋 b0d6ab8(2026-07-27) · 백엔드=FastAPI 0.115 + Python(Poetry) · 프론트엔드=React 18 + Vite 6 + TypeScript · 지원 스택 6종 · 백엔드 테스트 함수 약 260개)
목차
  1. 한 줄 정체
  2. 왜 지금 뜨는가
  3. 무엇을 해결하나
  4. 전체 구조
  5. 핵심 설계 · 동작 원리
  6. 기술 스택 · 의존성
  7. 설치 · 요구사항
  8. 실제 사용 흐름
  9. 함정 · 오해 방지
  10. 대안 비교
  11. 정리 · 누가 쓰면 좋은가

1한 줄 정체

"이미지·영상·텍스트를 받아 동작하는 프론트엔드 코드를 스트리밍으로 만들어주는 오픈소스 AI 에이전트"

screenshot-to-code를 한 문장으로 줄이면 이렇다: 사용자가 스크린샷(또는 화면 녹화 영상, 또는 텍스트 설명)을 올리면, 여러 개의 AI 모델이 동시에 후보 코드를 만들어 실시간으로 화면에 스트리밍해 주는 웹 앱이다. 프론트엔드는 React로 만든 채팅형 UI, 백엔드는 FastAPI로 만든 WebSocket 서버다.

다만 "AI가 이미지를 보고 코드를 뱉는다"는 설명은 절반만 맞다. 실제로는 모델이 파일 생성·수정·이미지 편집·자체 스크린샷 촬영 같은 도구(tool)를 스스로 호출하며 여러 턴에 걸쳐 작업하는 에이전트다. 한 번의 프롬프트로 끝나는 게 아니라, 만들고 → 스스로 찍어보고 → 어색하면 고치는 루프를 돈다.

한 줄 비유

"사진 한 장을 보고 그대로 따라 그리되, 그린 다음 거울에 비춰보고 스스로 고치는 화가"

단순한 이미지-투-텍스트 변환기라면 사진을 한 번 보고 붓질을 끝낸다. screenshot-to-code의 에이전트는 그림을 그린 뒤(create_file) 자기가 그린 그림을 실제로 렌더링해서 다시 눈으로 확인하고(screenshot_preview 도구 — 데스크탑·모바일 화면을 헤드리스 브라우저로 직접 찍어 본다), 어색한 부분이 있으면 부분만 고친다(edit_file).

게다가 원본 사진에 있던 로고나 아이콘은 대충 다시 그리지 않고, 사진에서 그 부분만 오려내(extract_assets) 재사용한다. "보고 베끼는" 수준을 넘어 "확인하고 고치는" 단계까지 들어간 게 이 프로젝트의 정체다.

용어
에이전트 (agent) · 도구 호출 (tool-calling)
AI 모델이 한 번에 답을 내놓고 끝나는 게 아니라, "이 파일을 만들어라", "이 이미지를 편집해라" 같은 정해진 도구(함수)를 스스로 선택해 호출하고, 그 결과를 다시 보고 다음 행동을 정하는 반복 루프로 일하는 방식. 이 저장소의 backend/agent/ 폴더 전체가 이 루프를 구현한다.

2왜 지금 뜨는가

"바이브 코딩" 붐 · 멀티모달 LLM의 발전 · 평가로 검증하는 오픈소스 드묾

2023년 말 처음 공개됐을 때만 해도 이 프로젝트는 "GPT-4 Vision으로 스크린샷을 코드로 바꿔본다"는 단순한 실험에 가까웠다. 지금(2026년) 다시 주목받는 이유는 크게 세 가지로 나뉜다.

① "바이브 코딩(vibe coding)" 시대의 표준적 니즈

v0·Lovable·Bolt 같은 상용 서비스가 "말만 하면 앱이 나온다"는 경험을 대중화하면서, 디자인 이미지 한 장을 코드로 바꾸는 일은 더 이상 특이한 요청이 아니라 프론트엔드 개발의 표준적인 시작점이 됐다. screenshot-to-code는 이 흐름에서 오픈소스로 직접 돌릴 수 있는 몇 안 되는 선택지다.

② 이미지 인식을 넘어 영상·자산 재사용까지

Gemini 계열 모델의 멀티모달 능력이 좋아지면서, 이 저장소는 정지 이미지뿐 아니라 화면 녹화 영상을 그대로 앱으로 재현하는 기능과, 스크린샷 속 실제 로고·아이콘을 잘라내어 재사용하는 자산 추출 기능까지 갖췄다. "그럴듯하게 새로 그리기"가 아니라 "원본 그대로 재사용하기"가 품질 차이를 만든다.

③ 모델을 감으로 고르지 않고 평가(evals)로 고른다

backend/routes/model_choice_sets.py의 커밋 이력을 보면, 어떤 모델 조합을 기본값으로 쓸지가 "8개 브리프 × 7개 모델을 채점한 평가 세션" 같은 구체적인 근거로 몇 주 단위로 갱신된다. 개인 프로젝트 수준의 오픈소스치고는 드물게, 모델 선택 자체가 데이터로 검증되는 파이프라인(backend/evals/, backend/run_evals.py)을 갖추고 있다.

경쟁 지형에서의 위치
"닫힌 SaaS의 편의" vs "오픈소스의 통제권" 사이

v0·Bolt 같은 서비스는 편하지만 남의 서버, 남의 모델에 묶인다. screenshot-to-code는 자신의 API 키(BYOK, bring-your-own-key)로 로컬에서 직접 돌릴 수 있어 코드와 이미지가 외부로 나가는 경로를 스스로 통제할 수 있다. 대신 인프라(스크린샷 렌더링용 브라우저 설치 등)를 직접 챙겨야 한다는 트레이드오프가 있다.

3무엇을 해결하나

"디자인을 코드로 옮기는 반복 노동"을 스택 6종·입력 3종으로 자동화

프론트엔드 개발에서 가장 지루하면서도 없앨 수 없는 단계가 "디자인 이미지를 보면서 픽셀 단위로 마크업을 손으로 옮겨 적는 일"이다. screenshot-to-code는 이 단계를 다음 세 가지 축으로 자동화한다.

① 입력의 다양성 — 이미지 / 텍스트 / 영상

custom_types.pyInputMode가 정의하듯, 입력은 이미지(스크린샷·피그마 내보내기)뿐 아니라 텍스트만으로 새 화면을 생성하거나, 화면 녹화 영상을 그대로 재현하는 것까지 지원한다. "디자인 파일이 없어도, 동작하는 화면을 녹화한 영상만 있어도" 프로토타입을 만들 수 있게 문턱을 낮췄다.

② 출력의 다양성 — 6개 스택

frontend/src/lib/stacks.ts / backend/prompts/prompt_types.py에 정의된 StackHTML+CSS, HTML+Tailwind, React+Tailwind, Vue+Tailwind, Bootstrap, Ionic+Tailwind 6종이다. 같은 디자인 한 장을 팀이 실제로 쓰는 프레임워크에 맞춰 바로 뽑아낼 수 있다.

③ "새로 만들기"뿐 아니라 "이어서 고치기"

생성은 한 번으로 끝나지 않는다. generation_typecreate(새로 생성)와 update(기존 코드 수정) 두 갈래로 나뉘고, 사용자가 미리보기 화면에서 요소 하나를 클릭해 선택하면 그 요소에 해당하는 코드 부분만 정확히 골라 고치는 "타겟 요소 편집"까지 지원한다(자세한 동작은 s5).

4전체 구조

"React 클라이언트 ↔ WebSocket ↔ 6단계 파이프라인 ↔ 병렬 에이전트 N개" 4계층

이 프로젝트의 뼈대는 하나의 WebSocket 연결 위에서, 여러 개의 AI 에이전트가 동시에 각자의 후보 코드를 만들어 스트리밍하는 구조다. 프론트엔드는 그 결과를 "변형(variant)" 탭으로 나란히 보여주고 사용자가 마음에 드는 것을 고른다.

┌────────────────────────────────────────────────────────────────┐ │ SCREENSHOT-TO-CODE 4계층 구조 │ └────────────────────────────────────────────────────────────────┘ [1] 프론트엔드 (React 18 + Vite, frontend/src) generateCode.ts — new WebSocket(`${WS_BACKEND_URL}/generate-code`) · 스크린샷/영상/텍스트 + 스택 선택값을 JSON으로 전송 · setCode / toolStart / toolResult / assistant / thinking 이벤트 수신 │ WebSocket (단일 연결, 양방향 JSON 메시지) ▼ [2] WS 라우트 + 6단계 파이프라인 (FastAPI, backend/routes/generate_code.py) router.websocket("/generate-code") ├ WebSocketSetupMiddleware → 연결 수락/종료 ├ ParameterExtractionMiddleware → 스택·입력모드·API 키 파싱·검증 ├ StatusBroadcastMiddleware → variant 개수 통지 ├ PromptCreationMiddleware → 프롬프트 메시지 조립 (s5 참고) ├ CodeGenerationMiddleware → 모델 선택 + 에이전트 실행 (핵심) └ PostProcessingMiddleware → 후처리 훅 │ asyncio.gather(...) 로 variant 수만큼 동시 실행 ▼ [3] 에이전트 N개 (backend/agent/engine.py — AgentEngine) 각 variant = 독립된 AgentEngine 인스턴스 · ProviderSession(anthropic/openai/gemini 추상화) 로 모델과 대화 · AgentToolRuntime 으로 도구(create_file 등) 실제 실행 · 최대 30스텝까지 "생성→도구 호출→결과 반영" 루프 │ 각 스텝마다 send_message(...) 로 [2]를 거쳐 [1]로 이벤트 전달 ▼ [4] 외부 LLM · 이미지 API (OpenAI / Anthropic / Gemini / Replicate) 실제 추론과 이미지 생성이 일어나는 곳 — 이 저장소는 이들을 호출하는 오케스트레이션 계층일 뿐, 모델 자체를 갖고 있지 않다

이 구조에서 눈여겨볼 점은 "파이프라인은 순서(미들웨어 체인)를 책임지고, 에이전트는 각자의 turn(대화 순번)을 책임진다"는 역할 분리다. variant 하나가 실패해도(asyncio.gather(..., return_exceptions=True)) 나머지 variant는 그대로 진행된다 — 4개 중 1개가 API 오류를 내도 사용자는 나머지 3개 결과를 받아볼 수 있다.

5핵심 설계 · 동작 원리

WS 미들웨어 체인 · 에이전트 30스텝 루프 · 프롬프트 조립 3전략 · 자체 스크린샷 검증 · 실제 자산 추출

이 절이 이 문서에서 가장 두꺼운 이유는, screenshot-to-code의 진짜 가치가 "어떤 모델을 쓰는가"가 아니라 "모델을 어떻게 감싸서 신뢰할 수 있는 코드를 뽑아내는가"에 있기 때문이다. 아래 순서대로 실제 파일과 함수를 따라가 본다.

① WebSocket 하나에 얹은 6단계 미들웨어 파이프라인

backend/routes/generate_code.py체인 오브 리스판서빌리티(chain of responsibility) 패턴을 직접 구현한다. Middleware 추상 클래스(process(context, next_func))를 6개 이어 붙이고, Pipeline._wrap_middleware가 뒤에서부터 감싸 하나의 호출 체인을 만든다. 각 미들웨어는 PipelineContext라는 공유 상태 객체(dataclass)에 자기 결과를 채워 다음 단계로 넘긴다 — 예를 들어 ParameterExtractionMiddleware가 채운 context.extracted_paramsPromptCreationMiddleware가 읽는다.

용어
미들웨어 (middleware) · 파이프라인
요청 하나를 여러 개의 독립된 처리 단계로 쪼개, 각 단계가 "자기 할 일을 하고 다음 단계를 부르는" 구조로 잇는 설계. Express.js·Django의 미들웨어와 같은 발상이며, 이 저장소는 이를 WebSocket 요청 하나에 직접 구현했다.

② 에이전트 루프 — 최대 30스텝, "생성→도구 호출→결과 반영"

실제 코드 생성은 backend/agent/engine.pyAgentEngine._run_with_session이 담당한다. for _ in range(max_steps)(max_steps = 30) 루프를 돌며, 매 스텝마다 모델에게 스트리밍 응답을 받고(session.stream_turn(on_event)), 모델이 도구를 호출했으면 AgentToolRuntime.execute로 실제 실행한 뒤 그 결과를 다시 대화에 붙여(session.append_tool_results) 다음 스텝으로 넘어간다. 도구 호출이 없으면(=모델이 "이제 끝났다"고 판단하면) 루프를 종료하고 최종 코드를 반환한다.

여기에 두 가지 안전장치가 붙는다. 하나는 예산 한도다. config.pyGENERATION_MAX_COST_USD = 3.0을 넘기면 BudgetExceededError를 던져 그 variant를 중단시킨다(다만 이미 답을 낸 턴은 "돈을 이미 썼으니" 막지 않는다 — 코드 주석에 그 의도가 명시돼 있다). 다른 하나는 빈 결과 방지다. 일부 모델(주석에 gemini-3.6-flash가 관찰 사례로 적혀 있다)이 이미지 도구만 쓰고 create_file은 호출하지 않은 채 끝나버리는 경우가 있는데, 이를 조용히 "성공"으로 넘기면 평가(evals)가 오염되므로 EmptyOutputError로 명시적 실패 처리한다.

비유

"재료를 하나씩 갖다주는 조수를 둔 요리사"라고 생각하면 쉽다. 요리사(모델)는 "달걀 좀 가져와"라고 말하고(도구 호출), 조수(AgentToolRuntime)가 실제로 냉장고에서 꺼내 온다(도구 실행). 요리사는 달걀을 받아본 다음에야 다음 지시를 내린다. 이 왕복이 최대 30번까지 반복되고, 요리사가 "다 됐다"고 하면 멈춘다.

③ 프롬프트 조립 — 상황에 맞는 3가지 전략 중 하나를 고른다

backend/prompts/pipeline.pybuild_prompt_messages는 곧바로 프롬프트를 만들지 않는다. 먼저 prompts/plan.pyderive_prompt_construction_plan이 현재 상황(새 생성인지 수정인지, 히스토리가 있는지, 파일 스냅샷이 있는지)을 보고 세 가지 구성 전략 중 하나를 고른다.

전략언제담당 모듈
create_from_input완전히 새로운 생성 (이미지/텍스트/영상)prompts/create/{image,text,video}.py
update_from_history이전 대화 기록을 바탕으로 이어서 수정prompts/update/from_history.py
update_from_file_snapshot프론트가 현재 파일 내용을 스냅샷으로 함께 보낸 경우prompts/update/from_file_snapshot.py

공통 시스템 프롬프트는 prompts/system_prompt.pySYSTEM_PROMPT 하나로 관리된다. 이 안에 스택별 분기가 그대로 들어 있다 — 예를 들어 React를 고르면 "react@18.0.0" UMD 스크립트와 babel.min.js를, Vue를 고르면 vue.global.js를 CDN으로 넣으라는 지시가 텍스트로 박혀 있다. 여기에 선택적으로 prompts/design_system.py가 사용자가 지정한 "디자인 시스템" 텍스트 블록을 덧붙인다("충돌하면 디자인 시스템을 우선하라"는 지시와 함께).

④ 실시간 미리보기 스트리밍 — 통째로 안 보내고 조금씩 보낸다

모델이 create_file로 HTML 전체를 만드는 동안, 사용자는 그 결과가 다 끝날 때까지 기다리지 않는다. AgentEngine._stream_code_preview는 이미 만들어진 내용을 최대 18조각(max_chunks = 18), 최소 200자 단위(min_step = 200)로 잘라 setCode 이벤트를 반복 전송한다. 도구 호출이 스트리밍 중(delta)일 때도 _handle_streamed_tool_delta가 40자 이상 늘어날 때마다 미리 화면을 갱신한다 — "다 만들고 나서 보여주기"가 아니라 "만드는 과정 자체를 보여주기"다.

⑤ 프로바이더 추상화 — OpenAI/Anthropic/Gemini를 같은 인터페이스로

backend/agent/providers/ 아래 base.py(ProviderSession, StreamEvent 공통 타입)와 factory.py(create_provider_session)가 세 회사의 서로 다른 스트리밍·도구 호출 API를 하나의 세션 인터페이스 뒤로 감춘다. AgentEngine 입장에서는 session.stream_turn(on_event)·session.append_tool_results(...)만 알면 되고, 실제로 어느 회사의 API를 부르는지는 anthropic/·openai.py·gemini.py 각 구현이 숨긴다. 새 모델 프로바이더를 추가할 때 에이전트 루프 코드를 건드릴 필요가 없는 구조다.

⑥ 자체 스크린샷 검증 — 만든 걸 스스로 눈으로 확인한다

backend/agent/tools/screenshot_preview.pyrun_screenshot_preview는 Playwright로 띄운 헤드리스 Chromium에서 지금까지 만든 HTML을 데스크탑·모바일 두 뷰포트로 실제 렌더링해 스크린샷을 찍고, 그 이미지를 모델에게 멀티모달 입력으로 다시 보여준다. 코드 주석에는 "이 스크린샷은 보기 위한 것이지 저장하기 위한 것이 아니다 — 모델이 자기 작업을 확인하는 용도일 뿐, 결과물에 끼워 넣는 자산으로 저장하지 않는다"고 명시돼 있다. 시스템 프롬프트도 "레이아웃이 깨지거나 요소가 겹치면 edit_file로 고치라"고 이 도구 사용을 직접 지시한다.

설계가 좋은 이유
"눈이 없는 화가"에게 거울을 쥐여준 것

텍스트만 보고 HTML을 생성하는 모델은 자기가 만든 CSS가 실제로 어떻게 보이는지 알 방법이 없다. 렌더링 결과를 다시 이미지로 넣어주는 이 한 수가, "그럴듯한 마크업"과 "실제로 안 깨지는 화면" 사이의 차이를 크게 좁힌다. Playwright/Chromium이 없는 환경에서는 이 도구 자체가 조용히 비활성화된다(preview_screenshot/registry.pyprobe_screenshot_preview가 가용성을 한 번 확인해 캐시한다).

⑦ 실제 자산 추출 — 로고를 다시 그리지 않고 오려낸다

backend/asset_extraction.pygemini-3.6-flash구조화 출력(Pydantic 스키마)을 강제해 쓴다. AssetDetection 모델은 요청 ID, 원본 이미지 중 몇 번째인지, box_2d(0~1000으로 정규화된 좌표), 라벨을 반환하도록 스키마로 못박는다. 이렇게 받은 좌표로 원본 스크린샷에서 그 영역만 실제로 잘라내(crop) 코드에 삽입한다 — 모델이 "로고 비슷한 이미지를 새로 생성"하는 게 아니라 "원본에 있던 그 로고 그대로"를 쓰게 만드는 장치다.

자산 자체를 새로 만들어야 할 때(원본에 없거나 배경에 가려진 경우)는 image_generation/generation.pyprocess_tasks가 담당하며, 프롬프트를 최대 20개씩 묶어(REPLICATE_BATCH_SIZE = 20) asyncio.gather로 동시에 Replicate API(기본 이미지 모델, 코드 내 flux_2_klein 분기 포함)를 호출한다. 투명 배경이 필요하면 별도의 remove_background 도구가, 저해상도 확대가 필요하면 edit_image(CSS로 늘리지 말라고 시스템 프롬프트가 명시적으로 금지한다)가 처리한다.

⑧ 영상 → 앱 변환 — 영상을 프레임 단위로 쪼개지 않는다

prompts/create/video.pybuild_video_prompt_messages를 보면 흥미로운 점이 있다. 영상을 미리 프레임으로 분해하거나 편집하는 별도 처리 없이, 영상 데이터 URL을 그대로 image_url 타입의 메시지 파트에 담아 Gemini에 통째로 넘긴다. "영상 속 사용자 상호작용과 화면 전환을 그대로 관찰해서, 같은 상호작용에 같은 결과가 나오도록 기능까지 동작하게 만들라"는 지시가 프롬프트에 그대로 박혀 있다. 영상 모드는 Gemini API 키가 반드시 있어야 하고(routes/model_choice_sets.pyVIDEO_VARIANT_MODELS — 두 개의 Gemini 모델만), variant 수도 2개로 고정된다(NUM_VARIANTS_VIDEO = 2).

⑨ 모델 큐레이션 — 하드코딩이지만 근거가 있는 하드코딩

routes/model_choice_sets.py는 겉보기엔 "어떤 상황에 어떤 모델을 쓸지" 정해둔 평범한 상수 목록이다. 그런데 각 목록 위 주석을 읽으면 "2026-07-27, 8개 브리프 × 7개 모델을 채점한 'Text evals v1' 세션 기준으로 교체" 같은 구체적 근거가 남아 있다. backend/evals/(core.py·runner.py·sessions.py·sets.py)와 run_evals.py가 이 평가를 실제로 돌리는 하니스다. API 키 조합(OpenAI/Anthropic/Gemini 중 몇 개를 가졌는지)과 입력 모드·생성 타입에 따라 ModelSelectionStage._get_variant_models가 적절한 모델 튜플을 골라, 모자라면 [A, B] 같은 목록을 num_variants만큼 순환(cycle)시켜 채운다.

⑩ 코드 후처리 — 도구를 안 썼을 때의 최후 안전망

정상적으로는 모델이 create_file 도구를 호출해 코드를 만들지만, 혹시 도구 대신 채팅 텍스트 안에 코드를 그대로 써버리는 경우를 대비해 codegen/utils.pyextract_html_content<html>...</html> 태그를 텍스트에서 직접 찾아내는 폴백을 제공한다. AgentEngine._finalize_response가 이 폴백을 최후 수단으로 사용한다.

6기술 스택 · 의존성

백엔드=FastAPI+Poetry · 프론트엔드=React+Vite+pnpm · 버전의 진실원은 lock 파일

스택 이름만 나열하면 흔한 "FastAPI + React" 조합처럼 보이지만, 버전을 어디서 확정하는지가 실제로 중요하다. 이 저장소에서 버전의 진실원(source of truth)은 항상 README의 설명문이 아니라 lock 파일이다 — backend/poetry.lock + backend/pyproject.toml, frontend/pnpm-lock.yaml + frontend/package.json.

백엔드 — Python(Poetry)

영역패키지 / 버전역할
웹서버FastAPI 0.115.6 · uvicorn 0.25 · websockets 14.1HTTP 라우트 + WebSocket 스트리밍
LLM SDKopenai 2.16.0 · anthropic 0.84 · google-genai 1.16.1세 프로바이더 각각의 공식 SDK
렌더링playwright 1.61자체 스크린샷 검증용 헤드리스 Chromium
이미지pillow 10.3 + pillow-heif 0.18이미지 처리, HEIC/HEIF(아이폰 사진) 디코딩
기타 명시 의존성moviepy 1.0.3 · langfuse 3.0.2pyproject.toml에 선언은 있으나 실제 소스에서 import된 곳이 없음(s9 참고)
패키지 매니저Poetry (Python ^3.10)poetry install / poetry run

프론트엔드 — TypeScript(pnpm)

영역패키지 / 버전역할
프레임워크React 18.2 · Vite 6.4 · TypeScript 5UI 렌더링 + 빌드
상태관리Zustand 4.5store/app-store.ts, store/project-store.ts
에디터·코드뷰CodeMirror 6 계열 · react-syntax-highlighter생성된 코드 표시/편집
UI 컴포넌트Radix UI(Accordion·Dialog·Tabs 등) · TailwindCSS 3.3디자인 시스템 프리미티브
테스트Jest 29src/tests/qa.test.ts 포함 9개 테스트 파일
패키지 매니저pnpm 10.32.1 (packageManager 필드로 고정)루트 package.jsonfrontend·backend를 workspaces로 묶음
용어
lock 파일 (lockfile)
"이 버전을 쓰겠다"고 선언한 것(package.json·pyproject.toml)과, 실제로 설치될 정확한 버전 조합을 고정해 기록한 파일(pnpm-lock.yaml·poetry.lock). 같은 선언이라도 lock 파일이 없으면 설치 시점마다 하위 버전이 달라질 수 있어, "버전의 진실"은 항상 lock 파일 쪽이다.

7설치 · 요구사항

API 키 최소 1개 · Poetry + pnpm · Playwright Chromium 설치 필수(스크린샷 검증용) · Docker 지원
항목내용
필수 키OPENAI_API_KEY · ANTHROPIC_API_KEY · GEMINI_API_KEY최소 1개. 셋 다 있으면 모델을 더 잘 섞어 쓴다
권장 키GEMINI_API_KEY(자산 추출·영상 모드에 필요) · REPLICATE_API_KEY(이미지 생성·편집·배경 제거)
백엔드 실행cd backend && poetry install && poetry run playwright install chromium && poetry run uvicorn main:app --reload --port 7001
프론트엔드 실행cd frontend && pnpm install && pnpm devhttp://localhost:5173
Docker루트에서 docker-compose up -d --build(백엔드 7001 · 프론트엔드 5173). 파일 변경 시 자동 재빌드는 안 됨
하드웨어GPU 불필요 — 추론은 전부 외부 API 호출. Chromium 렌더링용 메모리만 여유 있으면 됨
주의
"설치했는데 스크린샷 검증이 안 보인다"

Playwright의 playwright install chromium 단계를 건너뛰면 screenshot_preview 도구 자체가 조용히 비활성화된다(에러가 나지 않는다). 설정 다이얼로그(Settings)에 "Screenshot preview" 사용 가능 여부가 표시되니, 이걸로 확인하는 게 가장 빠르다.

또한 REPLICATE_API_KEY오직 backend/.env 파일로만 설정할 수 있다 — 다른 세 개 키와 달리 프론트엔드 설정 다이얼로그에서는 넣을 수 없다(README에 명시된 제약).

8실제 사용 흐름

업로드 → 스택 선택 → 4개(또는 2개) variant 동시 생성 → 실시간 비교 → 요소 선택 편집
단계 ①

입력 올리기 + 스택 고르기

스크린샷 이미지, 화면 녹화 영상, 또는 텍스트 설명 중 하나를 넣고, 원하는 출력 스택(예: React+Tailwind)을 고른다. 기존 코드가 있으면 "이어서 수정" 모드로 들어간다.

단계 ②

WebSocket 연결과 동시에 병렬 생성 시작

프론트엔드가 ws.send(JSON.stringify(params))로 파라미터를 보내면, 서버는 variantCount 이벤트로 몇 개의 후보(보통 4개, 수정은 2개, 영상은 2개)를 만들지 먼저 알려주고, 그만큼의 에이전트를 동시에 돌린다.

단계 ③

과정을 실시간으로 지켜본다

thinking(모델의 사고 과정)·assistant(짧은 채팅 응답)·toolStart/toolResult(도구 호출 시작/결과)·setCode(코드 미리보기 갱신) 이벤트가 순서대로 흘러온다. 사용자는 4개 탭을 오가며 "지금 어느 모델이 뭘 하고 있는지"를 볼 수 있다.

단계 ④

완료 후 비교하고, 필요하면 요소 하나만 콕 집어 수정

variant 4개가 각각 variantComplete를 받으면 미리보기 화면에서 나란히 비교할 수 있다. 마음에 드는 걸 고른 뒤, 그 위에서 요소를 직접 클릭해 선택하면 선택된 요소의 실제 렌더링 결과(outerHTML)를 함께 보내 "이 버튼만 빨간색으로 바꿔줘" 같은 국소 수정을 요청할 수 있다 — 시스템 프롬프트가 "선택된 요소는 라이브 DOM에서 캡처된 것이라 소스 코드와 다를 수 있으니(JSX는 className을 쓰고, Vue는 디렉티브를 쓴다), 태그·클래스·텍스트 내용으로 매칭해 그 부분만 고치라"고 구체적으로 지시한다.

9함정 · 오해 방지

Docker의 yarn 잔재 · 선언만 되고 안 쓰이는 의존성 2종 · 파이썬 버전 표기와 실제 · CI 부재
함정 ①
Docker로 빌드하면 yarn을, 로컬에서는 pnpm을 쓴다

저장소 전체의 진실은 pnpm이다 — 루트 package.jsonworkspaces·packageManager: "pnpm@10.32.1", frontend/pnpm-lock.yaml, README·CLAUDE.md 모두 pnpm install/pnpm dev를 안내한다. 그런데 frontend/Dockerfile은 여전히 COPY package.json yarn.lock /app/ + RUN yarn install로 되어 있다 — yarn.lock 파일 자체가 저장소에 없는데도 이 명령을 그대로 실행하려 한다.

docker-compose up으로 프론트엔드를 빌드하면 로컬 개발(pnpm)과 다른 경로를 타게 된다. Docker로 처음 시도했는데 의존성이 안 맞는다면, 이 불일치를 의심해볼 것.

함정 ②
"의존성에 있으니 쓰이겠지"는 항상 참이 아니다

backend/pyproject.toml에는 moviepylangfuse가 선언돼 있다. 하지만 실제 백엔드 소스 어디에도 import moviepyimport langfuse는 없다 — moviepy는 딱 한 곳, 테스트 파일(tests/test_prompts.py)에서 sys.modules에 미리 mock을 심어두는 방어 코드로만 등장한다. 실제 영상 처리는 moviepy로 프레임을 자르는 게 아니라, 영상 데이터를 통째로 Gemini에 넘기는 방식이다(s5-⑧). langfuse(LLM 트레이싱 도구)도 선언만 있을 뿐 실제로 트레이싱이 연결된 흔적이 없다 — "관측(observability)이 이미 켜져 있겠지"라고 가정하면 안 된다.

함정 ③
Python 버전 표기(^3.10)와 실제 개발 환경(3.12)이 다르다

pyproject.tomlpython = "^3.10"이라고 적혀 있지만, 저장소의 CLAUDE.md는 "Poetry 가상환경이 실제로는 3.12로 잡힌다(3.10이 만족되는 범위 안)"고 명시적으로 짚어둔다. 로컬에 정확히 3.10만 깔려 있어야 한다고 오해하지 말 것 — poetry run으로 실행하면 Poetry가 알아서 맞는 인터프리터를 찾는다.

함정 ④
CI 워크플로가 없다 — 테스트 정책은 사람(또는 에이전트)의 습관에 의존

.github/ 폴더에는 이슈 템플릿과 후원 설정(FUNDING.yml)만 있고 GitHub Actions 워크플로 파일은 없다. CLAUDE.md가 "모든 변경 후 pytest·pyright를 반드시 돌리라"고 규정하지만, 이는 PR 단계에서 자동으로 강제되는 게 아니라 기여자(사람이든 코딩 에이전트든)가 스스로 지켜야 하는 사내 규칙에 가깝다. "테스트가 260개 넘게 있으니 CI도 있겠지"라고 넘겨짚지 말 것.

함정 ⑤
모델 이름은 이 스냅샷 기준일 뿐, 매우 빠르게 갱신된다

routes/model_choice_sets.py의 모델 조합은 며칠~몇 주 단위로 평가 세션 결과에 따라 바뀐다(커밋 메시지에 그 근거가 남아 있다). 이 문서가 인용한 특정 모델명·버전은 2026-07-27 커밋 시점의 스냅샷일 뿐이므로, 실제로 코드를 볼 때는 반드시 저장소의 최신 model_choice_sets.py를 다시 확인해야 한다.

10대안 비교

openui · v0(Vercel) · bolt.new · fragments(e2b) — "오픈소스+로컬 실행+멀티모달 입력" 조합이 강점

"이미지/텍스트를 코드로 바꿔주는" 도구는 이제 하나의 장르다. screenshot-to-code가 어디에 서 있는지 보려면 대표적인 이웃들과 나란히 놓아봐야 한다.

프로젝트공개 방식핵심 차이점
screenshot-to-code오픈소스(MIT) + BYOK 로컬 실행이미지·영상·텍스트 3종 입력, 6개 스택, 도구 호출 에이전트 + 자체 스크린샷 검증, 평가 기반 모델 큐레이션
openui오픈소스프롬프트 → HTML/Tailwind 단일 스트리밍 생성 위주. 도구 호출형 에이전트 루프나 자체 렌더 검증은 없음
v0 (Vercel)비공개 SaaSNext.js/React·shadcn/ui 생태계에 깊게 통합, 클라우드 배포까지 원스톱. 모델·인프라를 직접 고를 수 없음
bolt.new (StackBlitz)비공개 SaaS(일부 OSS 코어)브라우저 안 WebContainer에서 풀스택(npm 설치 포함)까지 실행 — "이미지→정적 코드"보다 "대화형으로 앱 전체를 브라우저에서 굴리기"에 가까움
fragments (e2b)오픈소스 템플릿코드 실행 샌드박스(Next·Streamlit·Vue 등)가 중심 — 이미지 한 장을 정적 화면으로 재현하기보다 "대화하며 실행 가능한 코드 데모"에 특화
이 저장소만의 조합
"오픈소스 + 로컬 실행 + 멀티모달 입력 + 자체 검증"을 동시에 가진 몇 안 되는 선택지

v0·bolt는 편하지만 닫혀 있고, openui는 열려 있지만 단순한 스트리밍 생성에 머문다. screenshot-to-code는 소스가 완전히 공개돼 있으면서도, 이미지뿐 아니라 영상까지 입력으로 받고, 도구 호출 에이전트가 자기 결과를 스스로 스크린샷으로 확인하는 구조까지 갖췄다 — "공부하기 좋은 오픈소스 에이전트 코드베이스" 관점에서도 드문 조합이다.

11정리 · 누가 쓰면 좋은가

디자인→코드 프로토타이핑이 필요한 개발자 · 에이전트/WS 스트리밍 아키텍처를 배우고 싶은 학습자
이런 사람에게

디자인 목업을 빠르게 동작하는 코드로 바꾸고 싶은 프론트엔드 개발자

피그마 내보내기나 스크린샷 한 장으로 프로토타입 마크업을 빠르게 뽑아 시작점으로 삼고 싶을 때 유용하다. 6개 스택 중 팀이 실제로 쓰는 프레임워크를 바로 고를 수 있다.

이런 사람에게

여러 LLM 프로바이더를 비교·벤치마크하고 싶은 AI 엔지니어

backend/evals/run_evals.py는 그 자체로 "여러 모델에게 같은 과제를 시키고 채점해 비교하는" 하니스의 실물 예제다. 비용 추적(costs/pricing.py)까지 포함돼 있어 참고하기 좋다.

이런 사람에게

도구 호출 에이전트 · WebSocket 스트리밍 · 프로바이더 추상화를 배우고 싶은 학습자

미들웨어 파이프라인(s5-①), 30스텝 에이전트 루프(s5-②), 세 회사 API를 한 인터페이스 뒤로 감추는 패턴(s5-⑤)은 그 자체로 "실무 규모의 에이전트를 어떻게 구조화하는가"를 보여주는 좋은 사례다.

알아둘 것
오픈소스 저장소 ≠ 호스팅판(screenshottocode.com)

README가 안내하는 호스팅 제품(screenshottocode.com)은 이 저장소와 다른 hosted 브랜치에서, 별도의 비공개 SaaS 백엔드(CLAUDE.md../screenshot-to-code-saas로 명시)와 연결돼 동작한다. 이 저장소를 직접 클론해 로컬로 돌리는 것과, 유료 호스팅판을 쓰는 것은 같은 코어 로직을 공유하되 별개의 배포라는 점을 알아두면 혼동이 없다.