Files
C.E.L_Slide_test2/src/design_tokens.py
T
kmlee1andClaude Opus 4.6 0e4b8c091c Phase S: Claude HTML 직접 생성 + 독립 검증 시스템 도입
블록 선택 방식(Phase P/Q/R) 폐기 → Claude Sonnet이 영역별 HTML 직접 생성.
생성-검증 분리: content_verifier.py로 텍스트 보존/금지 콘텐츠/구조를 코드 검증.

주요 변경:
- src/html_generator.py: 4개 프롬프트 템플릿(BG/CORE/SIDEBAR/FOOTER) + 영역별 Claude 호출
- src/content_verifier.py: L1 텍스트 보존, L2 금지 콘텐츠, L3 구조 검증 + 재시도 루프
- src/html_validator.py: 보안 검증(script/iframe 제거)
- src/renderer.py: render_slide_from_html() 추가, area div overflow:hidden
- scripts/test_phase_s.py: generate_with_retry() 통합, step2b_verification 결과 저장
- 배경 라이트 디자인(#f8fafc), 개조식 어미 변환, 축약 금지 규칙

다음 과제: 폰트 위계(핵심14>본문12>배경10-12>첨부9-11) + 동적 컨테이너 계산

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 08:37:05 +09:00

178 lines
6.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Phase S-2: 디자인 토큰 + CSS 패턴 + 레이아웃 규칙을 프롬프트용으로 추출.
AI(Claude Sonnet)가 HTML을 생성할 때 참고하는 디자인 시스템 컨텍스트.
블록을 "선택"하는 것이 아니라, CSS 스타일을 "참고"하는 것.
Phase S 검증 결과 기반: 이미지 배치, 들여쓰기, 팝업 규칙 포함.
"""
from __future__ import annotations
from pathlib import Path
ROOT = Path(__file__).parent.parent
def load_design_tokens() -> str:
"""tokens.css 전문을 반환."""
tokens_path = ROOT / "static" / "tokens.css"
if tokens_path.exists():
return tokens_path.read_text(encoding="utf-8")
return ""
def load_base_css() -> str:
"""base.css 전문을 반환."""
base_path = ROOT / "static" / "base.css"
if base_path.exists():
return base_path.read_text(encoding="utf-8")
return ""
def get_block_css_patterns() -> str:
"""기존 블록에서 자주 쓰이는 CSS 패턴을 발췌하여 참고용 텍스트로 반환.
블록을 "선택"하기 위한 것이 아니라, AI가 HTML 생성 시 스타일을 "참고"하기 위한 것.
"""
return """
## 블록 CSS 패턴 참고 (스타일만 참고, 구조는 AI가 결정)
### 다크 배경 패턴
배경: linear-gradient(135deg, #1e293b, #0f172a)
텍스트: #ffffff, 제목: #93c5fd, 강조: #fbbf24
border-radius: 8px, padding: 14px 20px
불릿 마커: #60a5fa
카드 내부: rgba(255,255,255,0.06), border-left: 3px solid #60a5fa
### 밝은 카드 패턴
배경: #f8fafc, 테두리: 1px solid #e2e8f0
border-radius: 8px, padding: 12px 16px
제목: 15px bold #1e293b, 설명: 13px #475569
원형 번호: 32px, background: #2563eb, color: white
### 포함 관계 시각화 패턴
외곽 박스: border: 3px solid #2563eb, border-radius: 14px
배경: linear-gradient(180deg, #eff6ff, #dbeafe)
라벨 배지: background: #2563eb, color: white, border-radius: 10px, position: absolute top: -11px
내부 카드: background: white, border: 2px solid #93c5fd, border-radius: 8px
아이콘 원: background: linear-gradient(135deg, #93c5fd, #2563eb), 36px, border-radius: 50%
### 강조 메시지 패턴
배경: #f0f9ff, 테두리: 2px solid #bae6fd
텍스트: #0c4a6e, 강조 색상: #dc2626 (danger)
border-radius: 8px, padding: 10px 16px, text-align: center
### 배너/결론 패턴
배경: linear-gradient(135deg, #006aff, #00aaff)
텍스트: white, font-size: 15px bold
border-radius: 8px, padding: 14px 30px, text-align: center
### 테이블 패턴
헤더: background: #1e293b, color: white, padding: 8px 12px
행: border-bottom: 1px solid #e2e8f0, 짝수행: background: #f8fafc
첫 열: font-weight: 600, color: #1e293b
### 구분선 패턴
좌우 선: height: 1px, background: #cbd5e1
중앙 텍스트: 13px bold #64748b
### Sidebar 정의 카드 패턴
배경: #f8fafc, 테두리: 1px solid #e2e8f0
번호: 22px 원형, background: #2563eb
제목: 14px bold, 설명: 12px #475569
출처: 10px italic #94a3b8
"""
def get_layout_rules() -> str:
"""Phase S 검증 결과 기반 레이아웃 규칙."""
return """
## 레이아웃 규칙 (검증 결과 기반 — 반드시 따를 것)
### 들여쓰기 규칙 (절대 규칙)
- 불릿(•) 다음 줄은 반드시 불릿 옆 글자 시작 위치에 맞춤
- CSS: padding-left: 14px; text-indent: -14px;
- 하위 불릿(◦): padding-left: 28px; text-indent: -14px;
- 줄바꿈은 단어 기준 (word-break: keep-all)
### 이미지 배치 규칙
- 이미지가 있으면 float: right 또는 left로 텍스트와 어우러지게 (텍스트 감싸기)
- 이미지 크기: 컨테이너 너비의 30-40% (이미지 비율에 따라 판단)
- 이미지 border 없이 깔끔하게
- 이미지 바로 아래에 캡션 (이미지에 가까이 붙임, margin-top: 1-2px)
- 이미지 위치: 상단 텍스트 1-2줄은 전체 너비 → 그 아래에서 이미지 시작 (margin-top으로 조정)
- 이미지 아래 빈 공간에 관련 요소 배치 (팝업 링크, 부가 정보 등)
### 팝업 규칙
- 비교표, 상세 데이터 등은 <details>/<summary>로 팝업
- 팝업 링크: 상단 오른쪽에 텍스트 링크 (박스 불필요)
예: <summary>📊 DX와 BIM의 상세 비교</summary>
- 팝업 내용: position: absolute, background: white, box-shadow, z-index: 10
### 여백 규칙
- 빈 공간 최소화, 컨테이너를 꽉 채움
- 패딩/간격을 과도하게 넣지 않는다
- 이미지 아래 빈 공간에는 관련 요소(캡션, 팝업 링크 등) 배치
### 텍스트 보존 규칙 (절대 규칙)
- 원본 MDX의 80-95%를 그대로 사용
- source_data에 있는 텍스트를 그대로 가져온다
- 축약/요약/재작성 금지
- 삭제는 공간 부족 시에만, 뒤에서부터 자르기
- "상위개념", "하위기술" 같은 임의 라벨을 붙이지 않는다
"""
def build_design_context(container_specs: dict, images: list[dict] | None = None) -> str:
"""AI HTML 생성을 위한 전체 디자인 컨텍스트를 구성한다.
Args:
container_specs: 역할별 ContainerSpec
images: 이미지 정보 리스트 [{"path", "width", "height", "ratio", "topic_id"}, ...]
Returns:
AI 프롬프트에 포함할 디자인 시스템 텍스트
"""
if images is None:
images = []
tokens = load_design_tokens()
patterns = get_block_css_patterns()
# 컨테이너 정보
container_lines = []
for role, spec in container_specs.items():
container_lines.append(
f"- {role} ({spec.zone}): {spec.height_px}px × {spec.width_px}px, "
f"topics={spec.topic_ids}, weight={spec.weight}"
)
container_text = "\n".join(container_lines)
# 이미지 정보
image_text = ""
if images:
img_lines = []
for img in images:
img_lines.append(
f"- 경로: {img.get('path', '')}\n"
f" 크기: {img.get('width', '?')}px × {img.get('height', '?')}px\n"
f" 비율: {img.get('ratio', '?')} ({'가로형' if img.get('ratio', 1) > 1.5 else '세로형' if img.get('ratio', 1) < 0.7 else '정방형'})\n"
f" 소속 topic: {img.get('topic_id', '?')}"
)
image_text = "\n## 이미지 정보\n" + "\n".join(img_lines)
return f"""## 디자인 토큰 (CSS 변수 — 이 패턴을 참고)
{tokens}
## 슬라이드 프레임
- 크기: 1280px × 720px (고정)
- 폰트: Pretendard Variable
- 한국어 word-break: keep-all
- padding: 36px 40px 24px
## 컨테이너 스펙 (각 영역의 크기)
{container_text}
{image_text}
{patterns}
{get_layout_rules()}
"""