ScreenCoder: AI 기반 비주얼 코딩 어시스턴트 완전 가이드
⏱️ 예상 읽기 시간: 16분
서론
스크린샷만으로 코드가 자동 생성된다면? ScreenCoder는 바로 그 꿈을 현실로 만드는 도구입니다. AI 비전 모델의 발전으로 이제 디자인 목업, UI 스크린샷, 심지어 손으로 그린 와이어프레임까지도 완전한 코드로 변환할 수 있게 되었습니다.
더 이상 디자인을 보고 픽셀 퍼펙트하게 코드를 짤 필요가 없습니다. ScreenCoder가 이미지를 분석하여 HTML, CSS, React, Vue, Flutter 등 다양한 프레임워크의 코드를 자동으로 생성해드립니다.
ScreenCoder의 혁신적 특징
🎯 비주얼 코딩의 혁명
기존 개발 프로세스:
디자인 → 분석 → 수동 코딩 → 픽셀 퍼펙트 조정
ScreenCoder 프로세스:
이미지 → AI 분석 → 자동 코드 생성 → 미세 조정
🤖 지원하는 AI 모델들
- GPT-4 Vision (GPT-4V): OpenAI의 최첨단 비전 모델
- Claude 3.5 Vision: Anthropic의 고성능 비전 이해
- LLaVA: 오픈소스 비전-언어 모델
- Qwen-VL: 다국어 지원 비전 모델
📱 다양한 출력 형식
- HTML/CSS: 순수 웹 표준 코드
- React: JSX 컴포넌트 + CSS
- Vue.js: SFC(Single File Component)
- Flutter: Dart 위젯 코드
- SwiftUI: iOS 네이티브 UI
- Tailwind CSS: 유틸리티 퍼스트 CSS
설치 및 환경 설정
시스템 요구사항
# 지원 운영체제
- Windows 10/11
- macOS 11.0+
- Ubuntu 20.04+
# Python 환경
- Python 3.8 이상
- pip 또는 conda
# 권장 하드웨어
- RAM: 8GB 이상 (16GB 권장)
- GPU: CUDA 지원 GPU (선택사항)
- 인터넷: API 호출용 안정적 연결
ScreenCoder 설치
방법 1: pip 설치 (권장)
# ScreenCoder 설치
pip install screencoder
# 또는 개발 버전 설치
pip install git+https://github.com/leigest519/ScreenCoder.git
# 선택적 의존성 설치 (고급 기능용)
pip install "screencoder[all]"
방법 2: 소스 코드에서 설치
# 리포지토리 클론
git clone https://github.com/leigest519/ScreenCoder.git
cd ScreenCoder
# 가상환경 생성 (권장)
python -m venv screencoder-env
source screencoder-env/bin/activate # Windows: screencoder-env\Scripts\activate
# 의존성 설치
pip install -r requirements.txt
# 개발 모드로 설치
pip install -e .
방법 3: Docker 설치
# Docker 이미지 빌드
docker build -t screencoder .
# 컨테이너 실행
docker run -it --rm \
-v $(pwd)/images:/app/images \
-v $(pwd)/output:/app/output \
screencoder
API 키 설정
# .env 파일 생성
cat > .env << EOF
# OpenAI API 키 (GPT-4V 사용)
OPENAI_API_KEY=your_openai_api_key_here
# Anthropic API 키 (Claude Vision 사용)
ANTHROPIC_API_KEY=your_anthropic_api_key_here
# Google API 키 (Gemini Vision 사용, 선택사항)
GOOGLE_API_KEY=your_google_api_key_here
# 로컬 모델 설정 (선택사항)
OLLAMA_HOST=http://localhost:11434
LOCAL_VISION_MODEL=llava:13b
EOF
초기 설정 및 테스트
# config_test.py
from screencoder import ScreenCoder
from screencoder.models import GPT4Vision, ClaudeVision
import os
def test_installation():
"""설치 테스트"""
# 환경 변수 확인
openai_key = os.getenv('OPENAI_API_KEY')
anthropic_key = os.getenv('ANTHROPIC_API_KEY')
print("🔧 ScreenCoder 설치 테스트")
print(f"OpenAI API 키: {'✅ 설정됨' if openai_key else '❌ 없음'}")
print(f"Anthropic API 키: {'✅ 설정됨' if anthropic_key else '❌ 없음'}")
# ScreenCoder 인스턴스 생성 테스트
try:
if openai_key:
gpt4v_model = GPT4Vision(api_key=openai_key)
screencoder_gpt = ScreenCoder(model=gpt4v_model)
print("✅ GPT-4V 모델 로드 성공")
if anthropic_key:
claude_model = ClaudeVision(api_key=anthropic_key)
screencoder_claude = ScreenCoder(model=claude_model)
print("✅ Claude Vision 모델 로드 성공")
print("🎉 ScreenCoder 설치 완료!")
except Exception as e:
print(f"❌ 설치 테스트 실패: {e}")
if __name__ == "__main__":
test_installation()
기본 사용법
1. 첫 번째 코드 생성
# basic_example.py
from screencoder import ScreenCoder
from screencoder.models import GPT4Vision
from screencoder.outputs import HTMLOutput, ReactOutput
async def basic_code_generation():
"""기본 코드 생성 예제"""
# 1. ScreenCoder 초기화
model = GPT4Vision()
coder = ScreenCoder(model=model)
# 2. 이미지에서 HTML 코드 생성
html_result = await coder.generate_code(
image_path="./examples/landing_page.png",
output_format="html",
include_css=True,
responsive=True
)
print("생성된 HTML 코드:")
print(html_result.code)
# 3. 파일로 저장
html_result.save("./output/landing_page.html")
# 4. React 컴포넌트 생성
react_result = await coder.generate_code(
image_path="./examples/landing_page.png",
output_format="react",
component_name="LandingPage",
use_typescript=True
)
print("\n생성된 React 컴포넌트:")
print(react_result.code)
react_result.save("./output/LandingPage.tsx")
# 실행
import asyncio
asyncio.run(basic_code_generation())
2. 명령줄 인터페이스 사용
# 기본 HTML 생성
screencoder generate \
--input "./designs/homepage.png" \
--output "./output/homepage.html" \
--format html \
--model gpt4v
# React 컴포넌트 생성
screencoder generate \
--input "./designs/dashboard.png" \
--output "./output/Dashboard.tsx" \
--format react \
--typescript \
--component-name Dashboard
# 여러 이미지 배치 처리
screencoder batch \
--input-dir "./designs/" \
--output-dir "./output/" \
--format vue \
--model claude-vision
# 실시간 스크린샷 캡처 및 변환
screencoder capture \
--format react \
--auto-save \
--watch-clipboard
3. 고급 설정 및 프롬프트 커스터마이징
# advanced_config.py
from screencoder import ScreenCoder
from screencoder.models import GPT4Vision
from screencoder.prompts import CustomPrompt
class AdvancedScreenCoder:
def __init__(self):
self.model = GPT4Vision(
temperature=0.1, # 일관된 결과를 위해 낮은 값
max_tokens=4000,
timeout=30
)
self.coder = ScreenCoder(model=self.model)
async def generate_with_custom_prompt(self, image_path, requirements):
"""커스텀 프롬프트로 코드 생성"""
# 커스텀 프롬프트 생성
custom_prompt = CustomPrompt(
base_instruction="""
이 이미지를 분석하여 정확한 코드를 생성해주세요.
다음 요구사항을 반드시 준수해주세요:
""",
requirements=requirements,
style_guidelines="""
- 시맨틱 HTML 사용
- 접근성(a11y) 고려
- 모바일 퍼스트 디자인
- BEM CSS 방법론 적용
""",
additional_context="""
이 코드는 프로덕션 환경에서 사용될 예정입니다.
최고 품질의 코드를 생성해주세요.
"""
)
# 코드 생성
result = await self.coder.generate_code(
image_path=image_path,
output_format="html",
custom_prompt=custom_prompt,
include_comments=True,
optimize_performance=True
)
return result
async def generate_component_with_props(self, image_path, component_config):
"""Props와 상태를 고려한 React 컴포넌트 생성"""
react_prompt = CustomPrompt(
base_instruction=f"""
이 이미지를 분석하여 {component_config['name']} React 컴포넌트를 생성해주세요.
""",
requirements=[
f"TypeScript 사용: {component_config.get('typescript', True)}",
f"Props 인터페이스 정의",
f"상태 관리: {component_config.get('state_management', 'useState')}",
f"스타일링: {component_config.get('styling', 'CSS Modules')}",
"에러 처리 및 로딩 상태 포함",
"접근성 속성 추가",
"성능 최적화 (memo, useCallback 등)"
],
additional_context=f"""
컴포넌트는 다음과 같은 용도로 사용됩니다:
{component_config.get('description', '')}
재사용성과 확장성을 고려하여 설계해주세요.
"""
)
result = await self.coder.generate_code(
image_path=image_path,
output_format="react",
custom_prompt=react_prompt,
component_name=component_config['name'],
use_typescript=component_config.get('typescript', True)
)
return result
# 사용 예제
async def advanced_example():
"""고급 기능 사용 예제"""
advanced_coder = AdvancedScreenCoder()
# 1. 커스텀 요구사항으로 HTML 생성
html_requirements = [
"반응형 디자인 (모바일, 태블릿, 데스크톱)",
"다크모드 지원",
"애니메이션 효과 포함",
"SEO 최적화",
"웹 표준 준수"
]
html_result = await advanced_coder.generate_with_custom_prompt(
image_path="./designs/landing_page.png",
requirements=html_requirements
)
# 2. 고급 React 컴포넌트 생성
component_config = {
"name": "ProductCard",
"typescript": True,
"state_management": "useState + useContext",
"styling": "Styled Components",
"description": "전자상거래 사이트의 상품 카드 컴포넌트"
}
react_result = await advanced_coder.generate_component_with_props(
image_path="./designs/product_card.png",
component_config=component_config
)
print(f"HTML 코드 길이: {len(html_result.code)} 문자")
print(f"React 컴포넌트 길이: {len(react_result.code)} 문자")
asyncio.run(advanced_example())
실무 프로젝트: 디자인 시스템 자동 생성
프로젝트 구조
design_system_generator/
├── main.py # 메인 실행 파일
├── config/
│ ├── __init__.py
│ ├── settings.py # 프로젝트 설정
│ └── prompts.py # 커스텀 프롬프트
├── generators/
│ ├── __init__.py
│ ├── component_generator.py # 컴포넌트 생성기
│ ├── style_generator.py # 스타일 생성기
│ └── story_generator.py # Storybook 스토리 생성
├── processors/
│ ├── __init__.py
│ ├── image_processor.py # 이미지 전처리
│ └── code_optimizer.py # 코드 최적화
├── outputs/
│ ├── __init__.py
│ ├── file_manager.py # 파일 관리
│ └── documentation.py # 문서 생성
├── designs/ # 입력 디자인 파일들
├── output/ # 생성된 코드
└── requirements.txt
핵심 구현
# main.py
import asyncio
from pathlib import Path
from generators.component_generator import ComponentGenerator
from generators.style_generator import StyleGenerator
from generators.story_generator import StoryGenerator
from processors.image_processor import ImageProcessor
from outputs.file_manager import FileManager
from outputs.documentation import DocumentationGenerator
class DesignSystemGenerator:
def __init__(self, config):
self.config = config
self.image_processor = ImageProcessor()
self.component_generator = ComponentGenerator(config)
self.style_generator = StyleGenerator(config)
self.story_generator = StoryGenerator(config)
self.file_manager = FileManager(config.output_dir)
self.doc_generator = DocumentationGenerator()
async def process_design_directory(self, designs_dir):
"""디자인 디렉토리 전체 처리"""
design_files = list(Path(designs_dir).glob("*.png")) + \
list(Path(designs_dir).glob("*.jpg")) + \
list(Path(designs_dir).glob("*.jpeg"))
print(f"🎨 {len(design_files)}개의 디자인 파일 발견")
components = []
for design_file in design_files:
try:
print(f"📝 처리 중: {design_file.name}")
# 1. 이미지 전처리
processed_image = await self.image_processor.process(design_file)
# 2. 컴포넌트 유형 자동 감지
component_type = await self.detect_component_type(processed_image)
# 3. 컴포넌트 코드 생성
component = await self.component_generator.generate(
image_path=processed_image.path,
component_type=component_type,
name=design_file.stem
)
# 4. 스타일 시스템 생성
styles = await self.style_generator.generate_styles(
component=component,
design_tokens=processed_image.design_tokens
)
# 5. Storybook 스토리 생성
story = await self.story_generator.generate_story(
component=component,
variations=processed_image.variations
)
# 6. 파일 저장
saved_files = await self.file_manager.save_component(
component=component,
styles=styles,
story=story
)
components.append({
'component': component,
'styles': styles,
'story': story,
'files': saved_files
})
print(f"✅ {design_file.name} 처리 완료")
except Exception as e:
print(f"❌ {design_file.name} 처리 실패: {e}")
continue
# 7. 통합 문서 생성
documentation = await self.doc_generator.generate_design_system_docs(
components=components
)
await self.file_manager.save_documentation(documentation)
print(f"🎉 디자인 시스템 생성 완료! {len(components)}개 컴포넌트")
return components
async def detect_component_type(self, processed_image):
"""이미지 분석으로 컴포넌트 유형 자동 감지"""
detection_prompt = """
이 UI 디자인 이미지를 분석하여 가장 적합한 컴포넌트 유형을 판단해주세요.
가능한 유형들:
- button: 버튼 형태의 UI
- card: 카드 형태의 컨테이너
- form: 폼 입력 요소들
- navigation: 네비게이션 메뉴
- header: 헤더/상단 영역
- modal: 모달/팝업 창
- list: 목록/리스트 형태
- grid: 그리드 레이아웃
- chart: 차트/그래프
- other: 기타
응답 형식: {"type": "컴포넌트_유형", "confidence": 0.95, "description": "상세 설명"}
"""
result = await self.component_generator.model.analyze_image(
image_path=processed_image.path,
prompt=detection_prompt
)
return result.parsed_json
async def generate_design_tokens(self, components):
"""디자인 토큰 자동 추출 및 생성"""
# 모든 컴포넌트에서 공통 디자인 토큰 추출
colors = set()
fonts = set()
spacing = set()
borders = set()
shadows = set()
for comp in components:
if comp['styles'].design_tokens:
tokens = comp['styles'].design_tokens
colors.update(tokens.get('colors', []))
fonts.update(tokens.get('fonts', []))
spacing.update(tokens.get('spacing', []))
borders.update(tokens.get('borders', []))
shadows.update(tokens.get('shadows', []))
# 디자인 토큰 정리 및 명명
design_tokens = {
'colors': self.normalize_colors(colors),
'typography': self.normalize_fonts(fonts),
'spacing': self.normalize_spacing(spacing),
'borders': self.normalize_borders(borders),
'shadows': self.normalize_shadows(shadows)
}
# CSS 커스텀 프로퍼티 생성
css_tokens = self.generate_css_tokens(design_tokens)
# JavaScript 토큰 객체 생성
js_tokens = self.generate_js_tokens(design_tokens)
return {
'tokens': design_tokens,
'css': css_tokens,
'javascript': js_tokens
}
# generators/component_generator.py
from screencoder import ScreenCoder
from screencoder.models import GPT4Vision
class ComponentGenerator:
def __init__(self, config):
self.config = config
self.model = GPT4Vision(
api_key=config.openai_api_key,
temperature=0.1
)
self.screencoder = ScreenCoder(model=self.model)
async def generate(self, image_path, component_type, name):
"""컴포넌트 생성"""
# 컴포넌트 유형별 특화 프롬프트
type_specific_prompts = {
'button': self.get_button_prompt(),
'card': self.get_card_prompt(),
'form': self.get_form_prompt(),
'navigation': self.get_navigation_prompt(),
'modal': self.get_modal_prompt(),
'other': self.get_generic_prompt()
}
custom_prompt = type_specific_prompts.get(
component_type['type'],
self.get_generic_prompt()
)
# React 컴포넌트 생성
react_result = await self.screencoder.generate_code(
image_path=image_path,
output_format="react",
component_name=name,
custom_prompt=custom_prompt,
use_typescript=True,
include_props_interface=True,
include_tests=True
)
# Vue 컴포넌트도 생성 (선택사항)
vue_result = None
if self.config.include_vue:
vue_result = await self.screencoder.generate_code(
image_path=image_path,
output_format="vue",
component_name=name,
custom_prompt=custom_prompt,
use_typescript=True
)
return {
'name': name,
'type': component_type,
'react': react_result,
'vue': vue_result,
'metadata': {
'created_at': datetime.now().isoformat(),
'image_source': str(image_path),
'generator_version': self.config.version
}
}
def get_button_prompt(self):
"""버튼 컴포넌트 전용 프롬프트"""
return """
이 이미지는 버튼 UI 컴포넌트입니다. 다음 요구사항을 만족하는 React 컴포넌트를 생성해주세요:
필수 기능:
- variant prop (primary, secondary, outline, ghost)
- size prop (small, medium, large)
- disabled 상태 지원
- loading 상태 지원 (스피너 포함)
- icon 지원 (앞/뒤 위치)
- 접근성 속성 (ARIA)
- 키보드 네비게이션 지원
스타일링:
- CSS-in-JS 또는 styled-components 사용
- 호버, 포커스, 액티브 상태 정의
- 트랜지션 애니메이션
- 반응형 디자인
Props 인터페이스:
- onClick: () => void
- variant: 'primary' | 'secondary' | 'outline' | 'ghost'
- size: 'small' | 'medium' | 'large'
- disabled: boolean
- loading: boolean
- icon: ReactNode
- iconPosition: 'left' | 'right'
- children: ReactNode
"""
def get_card_prompt(self):
"""카드 컴포넌트 전용 프롬프트"""
return """
이 이미지는 카드 UI 컴포넌트입니다. 다음 요구사항을 만족하는 React 컴포넌트를 생성해주세요:
필수 기능:
- header, body, footer 섹션 지원
- 이미지 지원 (커버 이미지)
- 그림자/보더 스타일 옵션
- 클릭 가능한 카드 지원
- 로딩 스켈레톤 상태
레이아웃:
- flexbox 또는 grid 사용
- 반응형 디자인
- 다양한 크기 지원
Props 인터페이스:
- title: string
- description: string
- image: string
- actions: ReactNode[]
- onClick: () => void
- loading: boolean
- variant: 'default' | 'outlined' | 'elevated'
"""
# processors/image_processor.py
import cv2
import numpy as np
from PIL import Image, ImageEnhance
import pytesseract
class ImageProcessor:
def __init__(self):
self.supported_formats = ['.png', '.jpg', '.jpeg', '.bmp', '.tiff']
async def process(self, image_path):
"""이미지 전처리 및 분석"""
# 1. 이미지 로드 및 기본 정보 추출
image = Image.open(image_path)
image_info = {
'path': str(image_path),
'size': image.size,
'mode': image.mode,
'format': image.format
}
# 2. 이미지 품질 향상
enhanced_image = await self.enhance_image(image)
# 3. 텍스트 추출 (OCR)
extracted_text = await self.extract_text(enhanced_image)
# 4. 색상 팔레트 추출
color_palette = await self.extract_color_palette(enhanced_image)
# 5. 레이아웃 분석
layout_analysis = await self.analyze_layout(enhanced_image)
# 6. UI 요소 감지
ui_elements = await self.detect_ui_elements(enhanced_image)
# 7. 디자인 토큰 추출
design_tokens = await self.extract_design_tokens(
enhanced_image,
color_palette,
extracted_text
)
return ProcessedImage(
path=image_path,
info=image_info,
enhanced_image=enhanced_image,
text=extracted_text,
colors=color_palette,
layout=layout_analysis,
ui_elements=ui_elements,
design_tokens=design_tokens
)
async def enhance_image(self, image):
"""이미지 품질 향상"""
# 대비 향상
enhancer = ImageEnhance.Contrast(image)
enhanced = enhancer.enhance(1.2)
# 선명도 향상
enhancer = ImageEnhance.Sharpness(enhanced)
enhanced = enhancer.enhance(1.1)
# 노이즈 제거 (OpenCV 사용)
cv_image = cv2.cvtColor(np.array(enhanced), cv2.COLOR_RGB2BGR)
denoised = cv2.fastNlMeansDenoisingColored(cv_image, None, 10, 10, 7, 21)
# PIL 이미지로 변환
enhanced_final = Image.fromarray(cv2.cvtColor(denoised, cv2.COLOR_BGR2RGB))
return enhanced_final
async def extract_text(self, image):
"""OCR을 사용한 텍스트 추출"""
try:
# Tesseract OCR 설정
custom_config = r'--oem 3 --psm 6 -c tessedit_char_whitelist=0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!@#$%^&*()_+-=[]{}|;:,.<>?/~` '
# 텍스트 추출
text_data = pytesseract.image_to_data(
image,
config=custom_config,
output_type=pytesseract.Output.DICT
)
# 신뢰도가 높은 텍스트만 필터링
extracted_texts = []
for i, confidence in enumerate(text_data['conf']):
if int(confidence) > 60: # 60% 이상 신뢰도
text = text_data['text'][i].strip()
if text:
extracted_texts.append({
'text': text,
'confidence': confidence,
'bbox': {
'x': text_data['left'][i],
'y': text_data['top'][i],
'width': text_data['width'][i],
'height': text_data['height'][i]
}
})
return extracted_texts
except Exception as e:
print(f"OCR 텍스트 추출 실패: {e}")
return []
async def extract_color_palette(self, image, num_colors=8):
"""주요 색상 팔레트 추출"""
# 이미지를 RGB 배열로 변환
image_array = np.array(image)
pixels = image_array.reshape(-1, 3)
# K-means 클러스터링으로 주요 색상 추출
from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=num_colors, random_state=42)
kmeans.fit(pixels)
colors = kmeans.cluster_centers_.astype(int)
# 색상을 hex 형태로 변환
hex_colors = []
for color in colors:
hex_color = '#{:02x}{:02x}{:02x}'.format(color[0], color[1], color[2])
hex_colors.append(hex_color)
# 색상 사용 빈도 계산
labels = kmeans.labels_
color_counts = np.bincount(labels)
color_percentages = color_counts / len(pixels) * 100
palette = []
for i, (hex_color, percentage) in enumerate(zip(hex_colors, color_percentages)):
palette.append({
'hex': hex_color,
'rgb': colors[i].tolist(),
'percentage': round(percentage, 2),
'name': self.get_color_name(colors[i])
})
# 사용 빈도순으로 정렬
palette.sort(key=lambda x: x['percentage'], reverse=True)
return palette
def get_color_name(self, rgb):
"""RGB 값을 기반으로 색상 이름 추정"""
color_names = {
'white': [255, 255, 255],
'black': [0, 0, 0],
'red': [255, 0, 0],
'green': [0, 255, 0],
'blue': [0, 0, 255],
'yellow': [255, 255, 0],
'cyan': [0, 255, 255],
'magenta': [255, 0, 255],
'gray': [128, 128, 128]
}
min_distance = float('inf')
closest_color = 'unknown'
for name, color_rgb in color_names.items():
distance = np.sqrt(sum((a - b) ** 2 for a, b in zip(rgb, color_rgb)))
if distance < min_distance:
min_distance = distance
closest_color = name
return closest_color
class ProcessedImage:
def __init__(self, path, info, enhanced_image, text, colors, layout, ui_elements, design_tokens):
self.path = path
self.info = info
self.enhanced_image = enhanced_image
self.text = text
self.colors = colors
self.layout = layout
self.ui_elements = ui_elements
self.design_tokens = design_tokens
self.variations = self.generate_variations()
def generate_variations(self):
"""컴포넌트 변형 생성"""
variations = []
# 색상 변형
for color in self.colors[:3]: # 상위 3개 색상
variations.append({
'type': 'color',
'name': f"{color['name']}_variant",
'primary_color': color['hex']
})
# 크기 변형
for size in ['small', 'medium', 'large']:
variations.append({
'type': 'size',
'name': f"{size}_size",
'size': size
})
return variations
# 실행 예제
async def main():
"""메인 실행 함수"""
from config.settings import Config
# 설정 로드
config = Config()
# 디자인 시스템 생성기 초기화
generator = DesignSystemGenerator(config)
# 디자인 파일들 처리
components = await generator.process_design_directory("./designs/")
# 디자인 토큰 생성
design_tokens = await generator.generate_design_tokens(components)
print("🎨 디자인 시스템 자동 생성 완료!")
print(f"📦 생성된 컴포넌트: {len(components)}개")
print(f"🎯 추출된 디자인 토큰: {len(design_tokens['tokens'])}개")
if __name__ == "__main__":
asyncio.run(main())
고급 활용 사례
1. 실시간 디자인-코드 동기화
# realtime_sync.py
import asyncio
import time
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from screencoder import ScreenCoder
class DesignFileWatcher(FileSystemEventHandler):
"""디자인 파일 변경 감지"""
def __init__(self, screencoder):
self.screencoder = screencoder
self.last_modified = {}
self.debounce_time = 2 # 2초 디바운스
def on_modified(self, event):
if event.is_directory:
return
file_path = event.src_path
# 이미지 파일만 처리
if not any(file_path.endswith(ext) for ext in ['.png', '.jpg', '.jpeg']):
return
current_time = time.time()
# 디바운스 처리
if file_path in self.last_modified:
if current_time - self.last_modified[file_path] < self.debounce_time:
return
self.last_modified[file_path] = current_time
# 비동기 코드 생성 실행
asyncio.create_task(self.regenerate_code(file_path))
async def regenerate_code(self, file_path):
"""파일 변경 시 코드 재생성"""
try:
print(f"🔄 파일 변경 감지: {file_path}")
# 코드 재생성
result = await self.screencoder.generate_code(
image_path=file_path,
output_format="react",
auto_save=True,
notify_completion=True
)
print(f"✅ 코드 재생성 완료: {result.output_path}")
# 브라우저 자동 새로고침 (선택사항)
await self.trigger_browser_refresh()
except Exception as e:
print(f"❌ 코드 재생성 실패: {e}")
async def trigger_browser_refresh(self):
"""개발 서버 자동 새로고침"""
# Hot Module Replacement 또는 웹소켓을 통한 새로고침
pass
class RealtimeSyncManager:
def __init__(self, watch_directory="./designs", output_directory="./src/components"):
self.watch_dir = watch_directory
self.output_dir = output_directory
self.screencoder = ScreenCoder()
async def start_watching(self):
"""실시간 감시 시작"""
# 파일 감시자 설정
event_handler = DesignFileWatcher(self.screencoder)
observer = Observer()
observer.schedule(event_handler, self.watch_dir, recursive=True)
# 감시 시작
observer.start()
print(f"👀 디자인 파일 감시 시작: {self.watch_dir}")
try:
while True:
await asyncio.sleep(1)
except KeyboardInterrupt:
observer.stop()
print("📴 감시 중단")
observer.join()
# 사용 예제
async def realtime_example():
sync_manager = RealtimeSyncManager()
await sync_manager.start_watching()
# asyncio.run(realtime_example())
2. Figma API 연동
# figma_integration.py
import requests
import asyncio
from screencoder import ScreenCoder
class FigmaIntegration:
"""Figma API와 ScreenCoder 연동"""
def __init__(self, figma_token, screencoder):
self.figma_token = figma_token
self.screencoder = screencoder
self.api_base = "https://api.figma.com/v1"
async def export_figma_designs(self, file_key, component_names=None):
"""Figma 디자인을 이미지로 내보내기"""
# 1. Figma 파일 정보 가져오기
file_info = await self.get_file_info(file_key)
# 2. 컴포넌트 목록 추출
components = self.extract_components(file_info, component_names)
# 3. 각 컴포넌트를 이미지로 내보내기
exported_images = []
for component in components:
image_url = await self.export_component_image(file_key, component['id'])
# 이미지 다운로드
image_path = await self.download_image(image_url, component['name'])
exported_images.append({
'component': component,
'image_path': image_path
})
return exported_images
async def generate_code_from_figma(self, file_key, component_names=None):
"""Figma 디자인에서 직접 코드 생성"""
# Figma에서 이미지 내보내기
exported_images = await self.export_figma_designs(file_key, component_names)
generated_components = []
for item in exported_images:
component_info = item['component']
image_path = item['image_path']
# ScreenCoder로 코드 생성
result = await self.screencoder.generate_code(
image_path=image_path,
output_format="react",
component_name=component_info['name'],
include_figma_metadata=True,
figma_component_id=component_info['id']
)
generated_components.append({
'figma_component': component_info,
'generated_code': result,
'image_path': image_path
})
return generated_components
async def get_file_info(self, file_key):
"""Figma 파일 정보 조회"""
headers = {
'X-Figma-Token': self.figma_token
}
response = requests.get(
f"{self.api_base}/files/{file_key}",
headers=headers
)
if response.status_code == 200:
return response.json()
else:
raise Exception(f"Figma API 오류: {response.status_code}")
def extract_components(self, file_info, component_names=None):
"""파일에서 컴포넌트 추출"""
components = []
def traverse_node(node):
if node.get('type') == 'COMPONENT':
component_name = node.get('name', '')
# 특정 컴포넌트만 필터링
if component_names is None or component_name in component_names:
components.append({
'id': node['id'],
'name': component_name,
'description': node.get('description', ''),
'metadata': node
})
# 자식 노드 탐색
for child in node.get('children', []):
traverse_node(child)
# 모든 페이지 탐색
for page in file_info['document']['children']:
traverse_node(page)
return components
async def export_component_image(self, file_key, component_id):
"""컴포넌트를 이미지로 내보내기"""
headers = {
'X-Figma-Token': self.figma_token
}
params = {
'ids': component_id,
'format': 'png',
'scale': 2 # 2x 해상도
}
response = requests.get(
f"{self.api_base}/images/{file_key}",
headers=headers,
params=params
)
if response.status_code == 200:
data = response.json()
return data['images'][component_id]
else:
raise Exception(f"이미지 내보내기 실패: {response.status_code}")
async def download_image(self, image_url, component_name):
"""이미지 다운로드"""
response = requests.get(image_url)
if response.status_code == 200:
filename = f"./figma_exports/{component_name}.png"
# 디렉토리 생성
import os
os.makedirs(os.path.dirname(filename), exist_ok=True)
with open(filename, 'wb') as f:
f.write(response.content)
return filename
else:
raise Exception(f"이미지 다운로드 실패: {response.status_code}")
# 사용 예제
async def figma_example():
"""Figma 연동 예제"""
figma_token = "your_figma_personal_access_token"
screencoder = ScreenCoder()
figma = FigmaIntegration(figma_token, screencoder)
# Figma 파일에서 컴포넌트 코드 생성
file_key = "your_figma_file_key"
component_names = ["Button", "Card", "Modal"] # 특정 컴포넌트만 처리
components = await figma.generate_code_from_figma(file_key, component_names)
for comp in components:
print(f"✅ {comp['figma_component']['name']} 컴포넌트 생성 완료")
print(f"📁 코드 파일: {comp['generated_code'].output_path}")
# asyncio.run(figma_example())
3. CI/CD 파이프라인 통합
# ci_cd_integration.py
import os
import json
import subprocess
from pathlib import Path
class ScreenCoderCIPipeline:
"""CI/CD 파이프라인에서 ScreenCoder 활용"""
def __init__(self, config_path="./screencoder-ci.json"):
self.config = self.load_config(config_path)
self.screencoder = ScreenCoder()
def load_config(self, config_path):
"""CI 설정 로드"""
with open(config_path, 'r') as f:
return json.load(f)
async def process_changed_designs(self, changed_files):
"""변경된 디자인 파일들 처리"""
generated_files = []
for file_path in changed_files:
if self.is_design_file(file_path):
try:
# 코드 생성
result = await self.screencoder.generate_code(
image_path=file_path,
output_format=self.config['output_format'],
output_directory=self.config['output_directory']
)
generated_files.append(result.output_path)
# 품질 검사
quality_check = await self.run_quality_checks(result.output_path)
if not quality_check['passed']:
raise Exception(f"품질 검사 실패: {quality_check['errors']}")
except Exception as e:
print(f"❌ {file_path} 처리 실패: {e}")
return False
# Git 커밋
if generated_files:
await self.commit_generated_files(generated_files)
return True
def is_design_file(self, file_path):
"""디자인 파일 여부 확인"""
design_extensions = ['.png', '.jpg', '.jpeg', '.figma']
return any(file_path.endswith(ext) for ext in design_extensions)
async def run_quality_checks(self, code_file_path):
"""생성된 코드 품질 검사"""
checks = []
# 1. ESLint 검사
eslint_result = await self.run_eslint(code_file_path)
checks.append(eslint_result)
# 2. TypeScript 컴파일 검사
if code_file_path.endswith('.tsx'):
ts_result = await self.run_typescript_check(code_file_path)
checks.append(ts_result)
# 3. 접근성 검사
a11y_result = await self.run_accessibility_check(code_file_path)
checks.append(a11y_result)
# 4. 성능 검사
perf_result = await self.run_performance_check(code_file_path)
checks.append(perf_result)
# 모든 검사 결과 통합
all_passed = all(check['passed'] for check in checks)
errors = [error for check in checks for error in check.get('errors', [])]
return {
'passed': all_passed,
'errors': errors,
'details': checks
}
async def run_eslint(self, file_path):
"""ESLint 검사 실행"""
try:
result = subprocess.run(
['npx', 'eslint', file_path, '--format', 'json'],
capture_output=True,
text=True
)
if result.returncode == 0:
return {'passed': True, 'tool': 'eslint'}
else:
errors = json.loads(result.stdout)
return {
'passed': False,
'tool': 'eslint',
'errors': errors
}
except Exception as e:
return {
'passed': False,
'tool': 'eslint',
'errors': [str(e)]
}
async def commit_generated_files(self, generated_files):
"""생성된 파일들 Git 커밋"""
# Git add
for file_path in generated_files:
subprocess.run(['git', 'add', file_path])
# Git commit
commit_message = f"🤖 ScreenCoder: {len(generated_files)}개 컴포넌트 자동 생성"
subprocess.run(['git', 'commit', '-m', commit_message])
# GitHub Actions 워크플로우 예제
github_workflow = """
# .github/workflows/screencoder.yml
name: ScreenCoder Auto Generation
on:
push:
paths:
- 'designs/**'
- 'figma-exports/**'
pull_request:
paths:
- 'designs/**'
jobs:
generate-code:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.9'
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install ScreenCoder
run: |
pip install screencoder
- name: Install Node dependencies
run: |
npm install
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v35
with:
files: |
designs/**
figma-exports/**
- name: Generate code from designs
if: steps.changed-files.outputs.any_changed == 'true'
env:
OPENAI_API_KEY: $
run: |
python ci_cd_integration.py --changed-files="$"
- name: Run tests
run: |
npm test
- name: Run linting
run: |
npm run lint
- name: Commit generated files
if: steps.changed-files.outputs.any_changed == 'true'
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add .
git diff --staged --quiet || git commit -m "🤖 Auto-generated components from designs"
git push
"""
성능 최적화 및 모니터링
1. 코드 생성 성능 최적화
# performance_optimizer.py
import asyncio
import time
from concurrent.futures import ThreadPoolExecutor
from screencoder import ScreenCoder
class PerformanceOptimizer:
def __init__(self, max_workers=4):
self.max_workers = max_workers
self.executor = ThreadPoolExecutor(max_workers=max_workers)
self.generation_cache = {}
async def batch_generate_optimized(self, image_paths, config):
"""최적화된 배치 생성"""
# 1. 이미지 해시 계산 (캐싱용)
image_hashes = await self.compute_image_hashes(image_paths)
# 2. 캐시에서 기존 결과 확인
cached_results = {}
new_images = []
for i, (path, hash_val) in enumerate(zip(image_paths, image_hashes)):
cache_key = f"{hash_val}_{config['output_format']}"
if cache_key in self.generation_cache:
cached_results[path] = self.generation_cache[cache_key]
else:
new_images.append((path, hash_val))
print(f"💾 캐시에서 {len(cached_results)}개 결과 로드")
print(f"🔄 새로 생성할 이미지: {len(new_images)}개")
# 3. 새 이미지들 병렬 처리
new_results = {}
if new_images:
tasks = []
semaphore = asyncio.Semaphore(self.max_workers)
for path, hash_val in new_images:
task = self.generate_with_semaphore(
semaphore, path, hash_val, config
)
tasks.append(task)
results = await asyncio.gather(*tasks, return_exceptions=True)
for (path, hash_val), result in zip(new_images, results):
if not isinstance(result, Exception):
new_results[path] = result
# 캐시에 저장
cache_key = f"{hash_val}_{config['output_format']}"
self.generation_cache[cache_key] = result
# 4. 결과 병합
all_results = {**cached_results, **new_results}
return all_results
async def generate_with_semaphore(self, semaphore, image_path, image_hash, config):
"""세마포어를 사용한 제한된 동시 실행"""
async with semaphore:
start_time = time.time()
screencoder = ScreenCoder()
result = await screencoder.generate_code(
image_path=image_path,
**config
)
generation_time = time.time() - start_time
print(f"✅ {image_path} 생성 완료 ({generation_time:.2f}초)")
return {
'result': result,
'generation_time': generation_time,
'image_hash': image_hash
}
async def compute_image_hashes(self, image_paths):
"""이미지 해시 계산 (캐싱용)"""
import hashlib
from PIL import Image
hashes = []
for path in image_paths:
# 이미지 파일의 해시 계산
with open(path, 'rb') as f:
file_hash = hashlib.md5(f.read()).hexdigest()
hashes.append(file_hash)
return hashes
class GenerationMetrics:
"""코드 생성 메트릭 수집"""
def __init__(self):
self.metrics = {
'total_generations': 0,
'successful_generations': 0,
'failed_generations': 0,
'total_time': 0,
'average_time': 0,
'cache_hits': 0,
'models_used': {},
'output_formats': {},
'error_types': {}
}
def record_generation(self, success, time_taken, model, output_format, error=None):
"""생성 메트릭 기록"""
self.metrics['total_generations'] += 1
self.metrics['total_time'] += time_taken
if success:
self.metrics['successful_generations'] += 1
else:
self.metrics['failed_generations'] += 1
if error:
error_type = type(error).__name__
self.metrics['error_types'][error_type] = \
self.metrics['error_types'].get(error_type, 0) + 1
# 모델별 통계
self.metrics['models_used'][model] = \
self.metrics['models_used'].get(model, 0) + 1
# 출력 형식별 통계
self.metrics['output_formats'][output_format] = \
self.metrics['output_formats'].get(output_format, 0) + 1
# 평균 시간 업데이트
self.metrics['average_time'] = \
self.metrics['total_time'] / self.metrics['total_generations']
def record_cache_hit(self):
"""캐시 히트 기록"""
self.metrics['cache_hits'] += 1
def get_performance_report(self):
"""성능 리포트 생성"""
success_rate = (self.metrics['successful_generations'] /
max(self.metrics['total_generations'], 1)) * 100
cache_hit_rate = (self.metrics['cache_hits'] /
max(self.metrics['total_generations'], 1)) * 100
return {
'summary': {
'total_generations': self.metrics['total_generations'],
'success_rate': f"{success_rate:.2f}%",
'average_time': f"{self.metrics['average_time']:.2f}초",
'cache_hit_rate': f"{cache_hit_rate:.2f}%"
},
'details': self.metrics
}
트러블슈팅 가이드
1. 일반적인 문제 해결
# troubleshooting.py
import asyncio
import logging
from screencoder import ScreenCoder
from screencoder.exceptions import *
class ScreenCoderTroubleshooter:
def __init__(self):
self.logger = logging.getLogger(__name__)
self.common_issues = {
'api_key_error': self.fix_api_key_error,
'image_quality_poor': self.fix_image_quality,
'generation_timeout': self.fix_timeout_issue,
'invalid_output': self.fix_invalid_output,
'memory_error': self.fix_memory_error
}
async def diagnose_and_fix(self, error, context):
"""오류 진단 및 자동 수정"""
issue_type = self.classify_error(error)
if issue_type in self.common_issues:
fix_function = self.common_issues[issue_type]
return await fix_function(error, context)
else:
return await self.generic_fix_attempt(error, context)
def classify_error(self, error):
"""오류 유형 분류"""
error_message = str(error).lower()
if 'api key' in error_message or 'unauthorized' in error_message:
return 'api_key_error'
elif 'timeout' in error_message or 'time out' in error_message:
return 'generation_timeout'
elif 'memory' in error_message or 'out of memory' in error_message:
return 'memory_error'
elif 'image' in error_message and 'quality' in error_message:
return 'image_quality_poor'
elif 'invalid' in error_message or 'malformed' in error_message:
return 'invalid_output'
else:
return 'unknown'
async def fix_api_key_error(self, error, context):
"""API 키 오류 수정"""
fixes = []
# 1. 환경 변수 확인
import os
if not os.getenv('OPENAI_API_KEY'):
fixes.append("OPENAI_API_KEY 환경 변수가 설정되지 않았습니다.")
# 2. API 키 형식 검증
api_key = os.getenv('OPENAI_API_KEY', '')
if api_key and not api_key.startswith('sk-'):
fixes.append("OpenAI API 키 형식이 올바르지 않습니다. 'sk-'로 시작해야 합니다.")
# 3. API 키 권한 확인
if api_key:
try:
# 간단한 API 호출로 키 유효성 검증
from openai import OpenAI
client = OpenAI(api_key=api_key)
client.models.list()
fixes.append("API 키는 유효하지만 다른 문제가 있을 수 있습니다.")
except Exception as e:
fixes.append(f"API 키 권한 문제: {str(e)}")
return {
'issue': 'API Key Error',
'fixes': fixes,
'suggested_actions': [
"1. .env 파일에 올바른 OPENAI_API_KEY 설정",
"2. API 키 권한 및 잔액 확인",
"3. 방화벽 또는 프록시 설정 확인"
]
}
async def fix_image_quality(self, error, context):
"""이미지 품질 문제 수정"""
image_path = context.get('image_path')
fixes = []
if image_path:
from PIL import Image
try:
img = Image.open(image_path)
# 이미지 크기 확인
if img.size[0] < 300 or img.size[1] < 300:
fixes.append("이미지 해상도가 너무 낮습니다. 최소 300x300 권장")
# 이미지 형식 확인
if img.format not in ['PNG', 'JPEG', 'JPG']:
fixes.append("지원되지 않는 이미지 형식입니다. PNG 또는 JPEG 사용 권장")
# 파일 크기 확인
import os
file_size = os.path.getsize(image_path)
if file_size > 10 * 1024 * 1024: # 10MB
fixes.append("이미지 파일이 너무 큽니다. 10MB 이하 권장")
# 자동 이미지 개선 시도
enhanced_path = await self.enhance_image_automatically(image_path)
fixes.append(f"자동 개선된 이미지 저장: {enhanced_path}")
except Exception as e:
fixes.append(f"이미지 분석 실패: {str(e)}")
return {
'issue': 'Image Quality',
'fixes': fixes,
'suggested_actions': [
"1. 고해상도 이미지 사용 (최소 300x300)",
"2. 명확하고 선명한 디자인 제공",
"3. PNG 형식 권장 (투명도 지원)",
"4. 적절한 대비와 색상 사용"
]
}
async def enhance_image_automatically(self, image_path):
"""이미지 자동 개선"""
from PIL import Image, ImageEnhance
import os
img = Image.open(image_path)
# 대비 향상
enhancer = ImageEnhance.Contrast(img)
img = enhancer.enhance(1.2)
# 선명도 향상
enhancer = ImageEnhance.Sharpness(img)
img = enhancer.enhance(1.1)
# 개선된 이미지 저장
base_name = os.path.splitext(image_path)[0]
enhanced_path = f"{base_name}_enhanced.png"
img.save(enhanced_path, "PNG")
return enhanced_path
# 자동 복구 시스템
class AutoRecoverySystem:
def __init__(self, screencoder):
self.screencoder = screencoder
self.troubleshooter = ScreenCoderTroubleshooter()
self.max_retry_attempts = 3
async def generate_with_auto_recovery(self, **kwargs):
"""자동 복구 기능이 있는 코드 생성"""
for attempt in range(self.max_retry_attempts):
try:
result = await self.screencoder.generate_code(**kwargs)
return result
except Exception as e:
print(f"🔄 시도 {attempt + 1}/{self.max_retry_attempts} 실패: {e}")
if attempt < self.max_retry_attempts - 1:
# 자동 진단 및 수정
diagnosis = await self.troubleshooter.diagnose_and_fix(e, kwargs)
print(f"🔧 진단 결과: {diagnosis['issue']}")
# 수정 사항 적용
kwargs = await self.apply_fixes(kwargs, diagnosis)
# 잠시 대기 후 재시도
await asyncio.sleep(2 ** attempt) # 지수 백오프
else:
# 최종 실패
print(f"❌ 모든 복구 시도 실패")
raise e
async def apply_fixes(self, kwargs, diagnosis):
"""진단 결과를 바탕으로 파라미터 수정"""
# 여기서 진단 결과에 따라 kwargs를 수정
# 예: 이미지 경로 변경, 모델 변경, 타임아웃 증가 등
if diagnosis['issue'] == 'Image Quality':
# 개선된 이미지 경로로 변경
for fix in diagnosis['fixes']:
if '자동 개선된 이미지' in fix:
enhanced_path = fix.split(': ')[1]
kwargs['image_path'] = enhanced_path
break
elif diagnosis['issue'] == 'Generation Timeout':
# 타임아웃 증가
kwargs['timeout'] = kwargs.get('timeout', 30) * 2
return kwargs
결론
ScreenCoder는 디자인과 개발 사이의 간극을 획기적으로 줄여주는 혁신적인 도구입니다. AI 비전 모델의 발전으로 이제 스크린샷만으로도 프로덕션 레디 코드를 생성할 수 있게 되었습니다.
🎯 핵심 가치
- 개발 생산성 극대화: 디자인 → 코드 변환 시간 95% 단축
- 픽셀 퍼펙트 구현: AI가 디자인을 정확히 분석하여 구현
- 다양한 프레임워크 지원: React, Vue, Flutter 등 원하는 형태로 출력
- CI/CD 통합: 자동화된 개발 워크플로우 구축
🚀 적용 분야
- 프론트엔드 개발: 빠른 UI 컴포넌트 생성
- 프로토타이핑: 아이디어를 빠르게 코드로 변환
- 디자인 시스템: 일관된 컴포넌트 라이브러리 구축
- 교육: 시각적 학습을 통한 코딩 교육
🔮 미래 전망
ScreenCoder는 No-Code/Low-Code 플랫폼의 다음 단계입니다. 앞으로는:
- 실시간 디자인-코드 동기화
- 음성 명령으로 UI 수정
- 자동 접근성 및 성능 최적화
- 다국어 UI 자동 생성
이 모든 기능이 통합되어 진정한 AI 기반 개발 환경을 만들어갈 것입니다.
참고 자료:
- ScreenCoder GitHub
- 지원 AI 모델: GPT-4V, Claude Vision, LLaVA, Qwen-VL
- 출력 형식: HTML/CSS, React, Vue, Flutter, SwiftUI
관련 키워드: #ScreenCoder
#AI코딩
#비주얼코딩
#UI-to-Code
#GPT-4V
#코드생성
#자동화