Phase N+O: 컨테이너 기반 레이아웃 + Step B 제거 + 전면 정리
- Phase N: catalog 개선, fallback 전면 제거, Kei API 무한 재시도, topic_id 버그 수정 - Phase O: 컨테이너 스펙 계산(비중→px), 블록 스펙 확정, 렌더러 container div - Step B(Sonnet) 제거: Kei(A-2)+코드로 대체. STEP_B_PROMPT/fallback/DOWNGRADE_MAP 삭제 - Selenium: container div 감지 추가 - catalog.yaml: ref_chars 구조 변환 + FAISS 재빌드 - 문서 전면 갱신: README, PROGRESS, IMPROVEMENT, Phase I~O md Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -158,31 +158,89 @@ def _preprocess_svg_data(block_type: str, block_data: dict[str, Any]) -> dict[st
|
||||
return block_data
|
||||
|
||||
|
||||
def _group_blocks_by_area(blocks: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
"""같은 area의 블록들을 하나로 그룹핑한다.
|
||||
def _group_blocks_by_area(
|
||||
blocks: list[dict[str, Any]],
|
||||
container_specs: dict | None = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Phase O: 같은 area의 블록들을 비중 기반 컨테이너로 그룹핑한다.
|
||||
|
||||
CSS Grid에서 같은 area에 여러 div가 있으면 겹치므로,
|
||||
같은 area의 블록 HTML을 합쳐서 하나의 div로 만든다.
|
||||
container_specs가 있으면 body zone 안에 역할별 고정 높이 컨테이너를 생성.
|
||||
"""
|
||||
grouped = OrderedDict()
|
||||
for block in blocks:
|
||||
area = block["area"]
|
||||
if area not in grouped:
|
||||
grouped[area] = {"area": area, "htmls": []}
|
||||
grouped[area]["htmls"].append(block["html"])
|
||||
grouped[area] = {"area": area, "blocks": []}
|
||||
grouped[area]["blocks"].append(block)
|
||||
|
||||
result = []
|
||||
for area, data in grouped.items():
|
||||
if len(data["htmls"]) == 1:
|
||||
html = data["htmls"][0]
|
||||
block_list = data["blocks"]
|
||||
|
||||
# Phase O: body zone에 컨테이너 스펙 적용
|
||||
if container_specs and area in ("body", "left", "right", "hero", "detail"):
|
||||
container_htmls = []
|
||||
assigned_ids = set()
|
||||
|
||||
role_order = ["배경", "본심"]
|
||||
for role in role_order:
|
||||
spec = container_specs.get(role)
|
||||
if not spec or spec.zone != area:
|
||||
continue
|
||||
|
||||
# 이 역할에 속하는 블록 찾기 (topic_id로 매칭)
|
||||
role_blocks = [
|
||||
b for b in block_list
|
||||
if b.get("_topic_id") in spec.topic_ids
|
||||
and id(b) not in assigned_ids
|
||||
]
|
||||
|
||||
# topic_id 매칭 안 되면 순서로 매칭
|
||||
if not role_blocks:
|
||||
for b in block_list:
|
||||
if id(b) not in assigned_ids:
|
||||
role_blocks.append(b)
|
||||
if len(role_blocks) >= len(spec.topic_ids):
|
||||
break
|
||||
|
||||
for b in role_blocks:
|
||||
assigned_ids.add(id(b))
|
||||
|
||||
if not role_blocks:
|
||||
continue
|
||||
|
||||
inner_html = "\n".join(b["html"] for b in role_blocks)
|
||||
font_size = spec.block_constraints.get("font_size_px", 15.2)
|
||||
padding = spec.block_constraints.get("padding_px", 20)
|
||||
|
||||
container_htmls.append(
|
||||
f'<div class="container-{role}" style="'
|
||||
f'height:{spec.height_px}px; '
|
||||
f'overflow:visible; '
|
||||
f'display:flex; flex-direction:column; gap:8px; '
|
||||
f'font-size:{font_size}px; '
|
||||
f'--spacing-inner:{padding}px; '
|
||||
f'--font-body:{font_size / 16:.3f}rem;">\n'
|
||||
f'{inner_html}\n</div>'
|
||||
)
|
||||
|
||||
# 미배정 블록
|
||||
for b in block_list:
|
||||
if id(b) not in assigned_ids:
|
||||
container_htmls.append(b["html"])
|
||||
|
||||
html = "\n".join(container_htmls)
|
||||
|
||||
elif len(block_list) == 1:
|
||||
html = block_list[0]["html"]
|
||||
else:
|
||||
# 여러 블록을 flex-column으로 세로 쌓기
|
||||
inner = "\n".join(data["htmls"])
|
||||
inner = "\n".join(b["html"] for b in block_list)
|
||||
html = (
|
||||
f'<div style="display:flex; flex-direction:column; '
|
||||
f'gap:var(--spacing-block); height:100%;">\n'
|
||||
f'{inner}\n</div>'
|
||||
)
|
||||
|
||||
result.append({"area": area, "html": html})
|
||||
|
||||
return result
|
||||
@@ -205,6 +263,11 @@ def render_multi_page(layout_concept: dict[str, Any]) -> str:
|
||||
block_type = block.get("type", "")
|
||||
block_data = block.get("data", {})
|
||||
|
||||
# 높이 자동 조치: _strip_sub_text 플래그 처리
|
||||
if block_data.get("_strip_sub_text"):
|
||||
block_data.pop("sub_text", None)
|
||||
block_data.pop("_strip_sub_text", None)
|
||||
|
||||
# P2-B: SVG 시각화 블록은 좌표 사전 계산
|
||||
block_data = _preprocess_svg_data(block_type, block_data)
|
||||
|
||||
@@ -226,13 +289,19 @@ def render_multi_page(layout_concept: dict[str, Any]) -> str:
|
||||
f'<div class="body-text">블록 템플릿 미발견: {block_type}</div>'
|
||||
)
|
||||
|
||||
# Phase N-3: max-height CSS 래퍼 제거.
|
||||
# 콘텐츠는 렌더링 전에 _max_chars로 맞춘다. CSS로 사후에 자르지 않는다.
|
||||
# overflow는 slide_measurer가 scrollHeight > clientHeight로 감지한다.
|
||||
|
||||
blocks_raw.append({
|
||||
"area": block.get("area", "main"),
|
||||
"html": rendered_html,
|
||||
"_topic_id": block.get("topic_id"), # Phase O: 컨테이너 매칭용
|
||||
})
|
||||
|
||||
# Fix 1: 같은 area 블록 그룹핑
|
||||
blocks_grouped = _group_blocks_by_area(blocks_raw)
|
||||
# Phase O: 비중 기반 컨테이너 그룹핑
|
||||
page_container_specs = layout_concept.get("_container_specs")
|
||||
blocks_grouped = _group_blocks_by_area(blocks_raw, container_specs=page_container_specs)
|
||||
|
||||
# A-1: area별 CSS 변수 override 주입
|
||||
area_styles = page.get("area_styles", {})
|
||||
|
||||
Reference in New Issue
Block a user