From 05d43a7999e1fa7da90cd9156ffbfdc5ca07f2ec Mon Sep 17 00:00:00 2001 From: kyeongmin Date: Wed, 15 Apr 2026 17:59:46 +0900 Subject: [PATCH] Refine README and detail preview layout --- README.md | 198 ++++++++++++++++++++++++++++------------- src/block_assembler.py | 29 +++--- 2 files changed, 152 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index f16881d..143971f 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,90 @@ -# Kei Design Agent +# C.E.L. Slide Pipeline -MDX 콘텐츠를 분석해 1280x720 슬라이드 HTML로 변환하는 파이프라인입니다. +MDX 기반 콘텐츠를 분석해 1280x720 슬라이드 HTML로 변환하는 파이프라인입니다. -이 문서는 "지금 실제 코드 기준으로 파이프라인이 어떻게 동작하는지"를 빠르게 파악하기 위한 개요 문서입니다. 과거 Phase 문서와 일부 legacy 경로는 남아 있지만, 아래 설명은 현재 메인 경로를 기준으로 정리했습니다. +이 문서는 현재 코드 기준으로 이 프로젝트가 무엇을 하는지, 어떤 프로세스로 동작하는지, 현재 어디까지 와 있는지, 앞으로 무엇을 개선하려는지를 빠르게 파악하기 위한 개요 문서입니다. -## 한눈에 보기 +## 무엇을 하는가 -파이프라인의 큰 흐름은 아래와 같습니다. +이 프로젝트는 MDX 문서를 입력으로 받아 다음 과정을 거쳐 슬라이드 결과물을 생성합니다. -1. MDX를 코드가 정규화한다. -2. Kei가 문서의 의미와 topic을 읽는다. -3. 코드는 `normalized.sections`를 기준으로 실제 슬라이드 구조를 다시 만든다. -4. 코드는 schema, recipe, tag를 바탕으로 블록을 고른다. -5. Type B는 코드가 템플릿을 조립해 `final.html`을 만든다. -6. Type A는 아직 AI/renderer 비중이 더 크다. -7. Selenium과 vision gate로 측정/검증한 뒤 run 산출물을 저장한다. +- 콘텐츠 정규화 +- 문서 의미 분석 +- 슬라이드 구조 해석 +- schema / recipe / block 선택 +- HTML 조립 +- fit / overflow 검증 +- 최종 산출물 저장 + +최종 산출물은 보통 다음 형태로 저장됩니다. + +- `final.html` +- `final_context.json` +- `steps/*.html` +- popup / detail HTML + +## 한눈에 보는 프로세스 + +```text +MDX Input + ↓ +Stage 0 Normalize + ↓ +Stage 1A/1B AI Meaning Analysis + ↓ +Phase Y Group / Schema / Recipe Parsing + ↓ +Stage 1.5a Space Allocation + ↓ +Stage 1.7 Block Selection + ↓ +Stage 1.8 Fit / Measure / Adjustment + ↓ +Stage 2 Assemble final slide HTML + ↓ +Stage 4 Validate with Selenium / Vision + ↓ +Stage 5 Save final artifacts +``` 핵심 원칙은 다음과 같습니다. - source of truth는 `normalized.sections` -- block 선택은 문서명 하드코딩이 아니라 shape, schema, tag 기반 +- 문서명 하드코딩보다 shape, schema, recipe, tag를 우선한다 - 흐름의 우선순위는 `구조 -> payload -> layout -> fit` -- popup/detail은 overflow를 덮는 임시 장치가 아니라 `메인 요약 + 상세 보기`의 2단 표현 계약 +- popup / detail은 overflow를 덮는 임시 장치가 아니라 `메인 요약 + 상세 보기`의 표현 계약이다 ## 타입 구조 -현재 메인 타입 선택은 사실상 `A`와 `B`입니다. +현재 메인 타입 선택은 사실상 `Type A`와 `Type B`입니다. ### Type A - 본문 외에 sidebar, reference, 부록성 영역이 함께 필요한 슬라이드 -- 현재는 Type B보다 덜 닫혀 있고, AI 생성 + renderer 경로 비중이 큽니다 +- 현재는 Type B보다 덜 닫혀 있고, AI 생성 + renderer 경로 비중이 더 큼 ### Type B -- top, bottom 같은 본문 zone 조합으로 해결되는 슬라이드 -- 현재 가장 안정적인 메인 경로입니다 -- 최근 구조화 작업은 대부분 이 Type B 경로를 중심으로 진행되었습니다 +- top / bottom 같은 본문 zone 조합으로 해결되는 슬라이드 +- 현재 가장 안정적인 메인 경로 +- 최근 구조화 작업은 대부분 이 Type B 경로를 중심으로 진행됨 ### Type B' / B'' -- 역사적으로 실험/호환 경로에서 나온 변형입니다 -- 문서와 일부 legacy 코드에 흔적이 남아 있습니다 -- 현재 메인 개념의 1급 타입으로 보기보다, 과거 흐름과 호환 레이어로 이해하는 편이 맞습니다 +- 역사적으로 실험/호환 과정에서 나온 변형 경로 +- 일부 legacy 코드와 과거 산출물에 흔적이 남아 있음 +- 현재 메인 1급 타입이라기보다, 과거 흐름과 검증된 표시 계약을 담고 있는 보조 경로에 가까움 ## 단계별 파이프라인 -아래는 현재 기준의 실질적인 단계입니다. - | 단계 | 담당 | 주요 파일 | 하는 일 | 주요 산출물 | |---|---|---|---|---| | Stage 0 | 코드 | `src/mdx_normalizer.py` | MDX를 정규화하고 sections, tables, images, popups로 분리 | `NormalizedContent` | | Stage 1A | AI (Kei/Opus) | `src/kei_client.py` | title, core message, topic, 초기 layout 힌트 추출 | `Analysis`, `Topic[]` | -| Phase Y | 코드 | `src/pipeline.py`, `src/section_parser.py` | `normalized.sections` 기반으로 group schema, recipe, zone/page_structure 결정 | `PageStructure`, `mdx_sections` | +| Phase Y | 코드 | `src/pipeline.py`, `src/section_parser.py` | `normalized.sections` 기반으로 group, schema, recipe, zone/page_structure 결정 | `PageStructure`, `mdx_sections` | | Stage 1B | AI (Kei/Opus) | `src/kei_client.py` | topic별 relation, source_data, 표현 힌트 보강 | 보강된 `Topic[]` | | Stage 1B-ST | AI (Kei/Opus) | `src/kei_client.py` | structured_text 생성 | `Topic.structured_text` | -| Stage 1.5a | 코드 | `src/space_allocator.py` | zone/container 크기, preset, font hierarchy 계산 | `containers`, `font_hierarchy` | +| Stage 1.5a | 코드 | `src/space_allocator.py` | zone / container 크기, preset, font hierarchy 계산 | `containers`, `font_hierarchy` | | Stage 1.7 | 코드 | `src/block_reference.py`, `templates/catalog.yaml` | tag_match, schema_match, fallback 기준으로 block 선택 | `references` | | Stage 1.8 | 코드 + Selenium + 일부 AI | `src/pipeline.py`, `src/slide_measurer.py` | fit 측정, overflow 확인, 재배분, 보정 | `fit_result`, `measurement` | | Stage 2 | 코드 중심 | `src/block_assembler.py` | Type B 기준 slide-base + block template + payload 조립 | `generated_html` | @@ -70,12 +99,12 @@ MDX 콘텐츠를 분석해 1280x720 슬라이드 HTML로 변환하는 파이프 지금 실전에서 가장 중요한 경로는 아래입니다. 1. Stage 0에서 MDX를 정규화 -2. Stage 1A/1B에서 Kei가 의미와 topic 추출 +2. Stage 1A / 1B에서 AI가 문서 의미와 topic 추출 3. Phase Y에서 코드가 `normalized.sections`를 읽고 page_structure를 다시 생성 -4. Stage 1.7에서 block 선택 -5. Stage 1.8에서 fit/overflow 검증 +4. Stage 1.7에서 block / recipe 후보 선택 +5. Stage 1.8에서 fit / overflow 검증 6. Stage 2에서 `assemble_slide_html_final()`로 최종 HTML 조립 -7. Stage 4/5에서 측정과 산출물 저장 +7. Stage 4 / 5에서 측정과 산출물 저장 Type B의 핵심 파일은 아래입니다. @@ -92,7 +121,7 @@ Type A는 현재도 살아 있지만, Type B만큼 단단하게 닫힌 상태는 - AI 생성 비중이 더 큼 - `src/renderer.py` 의존도가 더 큼 -- sidebar/reference 구조를 포함하는 쪽에서 의미가 큼 +- sidebar / reference 구조를 포함하는 문서에서 의미가 큼 ## schema -> recipe -> block @@ -118,14 +147,16 @@ block 이름이 아니라 표현 규칙입니다. - `single_block` - `two_col_text_visual` +- `two_col_text_detail` - `stacked_summary_detail` -recipe는 이런 계약을 가질 수 있습니다. +recipe는 보통 이런 계약을 가질 수 있습니다. -- left/right kind -- top/bottom kind +- left / right kind +- top / bottom kind - ratio - vertical align +- direct render 우선 여부 ### block @@ -138,23 +169,23 @@ recipe는 이런 계약을 가질 수 있습니다. - `compare-detail-gradient` - `card-icon-desc` -즉, "무슨 문서냐"가 아니라 "무슨 구조냐"를 먼저 읽고, 그 구조에 맞는 표현 규칙을 정한 뒤, 마지막에 구현 블록을 고르는 방향으로 가고 있습니다. +즉, “무슨 문서냐”보다 “무슨 구조냐”를 먼저 읽고, 그 구조에 맞는 표현 규칙을 정한 뒤, 마지막에 구현 block을 고르는 방향으로 가고 있습니다. ## popup / detail 계약 -popup은 지금 다음 철학으로 정리되는 중입니다. +popup은 현재 다음 철학으로 정리되는 중입니다. -- 메인 슬라이드에는 존 크기에 맞는 요약만 남긴다 -- 큰 표, 시각 컴포넌트, 과다한 bullet은 상세 popup으로 분리한다 +- 메인 슬라이드에는 zone 크기에 맞는 요약만 남긴다 +- 큰 표, 시각 컴포넌트, 과다 bullet은 상세 popup으로 분리한다 - 메인에서는 `자세히보기` 링크를 제공한다 현재 popup 관련 핵심은 아래입니다. -- `PopupItem` 모델이 도입되어 popup 데이터를 명시적으로 다룸 -- `popup_id`, `popup_file` 생애주기를 분리해 관리 중 +- `PopupItem` 모델로 popup 데이터를 명시적으로 다루기 시작함 +- `popup_id`와 `popup_file` 생애주기를 분리해 관리 중 - 최종 목표는 popup 판단을 휴리스틱이 아니라 명시적 contract로 만드는 것 -다만 아직 일부 구간엔 추측 로직과 이중 관리가 남아 있어, 이 부분은 계속 정리 중입니다. +다만 아직 일부 구간에는 추측 로직과 이중 관리가 남아 있어, 이 부분은 계속 정리 중입니다. ## run 산출물 구조 @@ -165,22 +196,22 @@ popup은 지금 다음 철학으로 정리되는 중입니다. - `final.html` - `final_context.json` - `steps/*.html` -- popup/detail html +- popup / detail HTML -### `final.html` +### final.html - 최종 렌더 결과 - 실제 눈으로 보는 산출물 -### `final_context.json` +### final_context.json - 각 단계 결과를 최종 context 형태로 저장 - block 선택, page_structure, measurement, quality_score 등을 확인할 때 가장 중요 -### `steps/*.html` +### steps/*.html -- 단계별 디버그/설명용 보드 -- 현재는 검토용으로 유용하지만, 일부 인코딩/설명 품질은 더 다듬을 필요가 있습니다 +- 단계별 디버그 / 설명용 보드 +- 현재도 검토용으로 유용하지만, 일부 인코딩과 설명 품질은 더 다듬을 필요가 있음 ## 자주 봐야 하는 파일 @@ -199,39 +230,83 @@ popup은 지금 다음 철학으로 정리되는 중입니다. - [templates/blocks/new/prerequisites-3col.html](templates/blocks/new/prerequisites-3col.html) - [templates/blocks/redesign/process-product-2col.html](templates/blocks/redesign/process-product-2col.html) - [templates/blocks/cards/compare-detail-gradient.html](templates/blocks/cards/compare-detail-gradient.html) -- [templates/blocks/slide-base.html](templates/blocks/slide-base.html) - 주의: 현재 삭제/legacy 정리 여부를 별도로 확인해야 하는 경로입니다. +- `templates/blocks/slide-base.html` ### 검증 / 측정 - [src/slide_measurer.py](src/slide_measurer.py) - [src/validators.py](src/validators.py) +- [src/step_visualizer.py](src/step_visualizer.py) -### 역사 / 계획 문서 +### 계획 / 히스토리 - [PIPELINE.md](PIPELINE.md) - [docs/history/PHASE-Y-PLAN.md](docs/history/PHASE-Y-PLAN.md) -- [ARCHITECTURE_OVERVIEW.md](ARCHITECTURE_OVERVIEW.md) -## 현재 상태 요약 +## 현재 상태 -### 잘 닫혀가는 것 +### 비교적 잘 닫혀가는 것 - Type B 메인 경로 - `normalized.sections` 기반 구조 해석 -- schema / recipe 기반 block selection -- `prerequisites-3col`, `process-product-2col` 같은 redesign 블록 자산화 -- popup/detail 2단 표현 계약의 초안 연결 +- schema / recipe 기반 block selection의 골격 +- redesign block 자산화 +- popup / detail 2단 표현 계약의 초안 연결 ### 아직 정리 중인 것 - Type A 전체 안정화 - popup을 완전한 source of truth로 정리 -- `tag_match` 와 `schema_match`의 완전한 동등 점수 비교 -- step 보드 인코딩/설명 품질 -- legacy 경로와 문서의 정리 +- tag_match 와 schema_match의 완전한 동등 점수 비교 +- step 보드 인코딩 / 설명 품질 +- fit loop의 공간 재분배 고도화 +- legacy 경로와 문서 정리 -## 읽는 방법 추천 +### 최근 Type B에서 특히 중요해진 방향 + +- recipe direct render가 block 선택에 끌려가지 않도록 구조 계약을 더 강하게 만든다 +- Type B direct render가 Type B'의 검증된 표시 계약을 최대한 재사용하도록 정리한다 +- sample은 복제 대상이 아니라 evaluation rule의 기준으로 사용한다 +- validation은 “같아야 한다”보다 “어긋나면 안 된다”에 초점을 둔다 + +## 향후 개선 방향 + +현재 이후의 개선 방향은 아래 축으로 정리됩니다. + +### 1. 구조 계약 강화 + +- top / bottom zone의 contract를 더 구체화 +- `parallel_cluster_plus_visual`, `full_text + detail_preview` 같은 recipe를 표현 범주 수준으로 강화 +- recipe가 block를 끌고 가는 것이 아니라 recipe가 block를 통제하도록 정리 + +### 2. 표시 계약 통합 + +- Type B direct render가 Type B'에서 이미 검증된 bullet / indent / body-text 구조를 재사용하도록 통합 +- `.rdr-*` 계열 신규 CSS를 계속 키우기보다, 기존 검증된 계약을 최대한 재사용 + +### 3. detail preview 개선 + +- popup source가 표면 `헤더 + 일부 행` +- popup source가 리스트면 `앞부분 몇 개` +- popup source가 컴포넌트면 구조화된 preview + +즉 “링크만 있는 상세”가 아니라 “상세가 있다는 걸 바로 이해할 수 있는 preview”를 만드는 방향 + +### 4. fit loop 고도화 + +지금은 주로 overflow 대응 중심이지만, 앞으로는 아래까지 확장하려고 합니다. + +- 빈 공간 감지 +- 이미지 확대 +- preview 행 수 조정 +- zone 비율 재배분 + +### 5. 검증판과 final 일치화 + +- `stage_4` 보드와 실제 `final_context.json` / `final.html`의 상태 차이를 줄이기 +- 검수 보드를 더 믿을 수 있는 상태로 정리 + +## 읽는 순서 추천 프로세스를 빠르게 파악하려면 아래 순서가 좋습니다. @@ -241,8 +316,7 @@ popup은 지금 다음 철학으로 정리되는 중입니다. 4. [src/block_assembler.py](src/block_assembler.py) 5. 최근 run의 `final_context.json` -히스토리까지 보려면 아래 문서를 이어서 보면 좋습니다. +히스토리와 설계 변화까지 보려면 아래 문서를 이어서 보면 좋습니다. - [PIPELINE.md](PIPELINE.md) - [docs/history/PHASE-Y-PLAN.md](docs/history/PHASE-Y-PLAN.md) - diff --git a/src/block_assembler.py b/src/block_assembler.py index a34a86a..2f24860 100644 --- a/src/block_assembler.py +++ b/src/block_assembler.py @@ -1297,7 +1297,8 @@ def _recipe_direct_render( text-decoration: underline; } .rdr-table-preview { - margin-top: 8px; + margin-top: 4px; + width: 100%; } .rdr-preview-table { border-collapse: collapse; @@ -1526,24 +1527,26 @@ def assemble_slide_html_final(ctx: "PipelineContext", title_text: str = "", meas # right sub_type 확인 — visual_detail이면 summary + popup right_sub_type = sub_types[1].get("sub_type", "") if len(sub_types) > 1 else "" - # 우측: 요약 + popup 링크 + # 우측: 제목(+자세히보기 우측) + 표 preview right_parts = [] - if len(sub_titles) > 1: - right_title = sub_titles[1] + right_title = sub_titles[1] if len(sub_titles) > 1 else "" + pf = popup_file_map.get(role_name, "") + + # 헤더 라인: 제목 좌측 + 자세히보기 우측 (한 줄) + if right_title or pf: + link_html = f'자세히보기 →' if pf else "" right_parts.append( - f'
' - f'{right_title}
' + f'
' + f'
{right_title}
' + f'{link_html}
' ) - # structured detail preview + exactly one link - if role_name in popup_file_map: - pf = popup_file_map[role_name] + + # 표 preview (전체 너비) + if pf: preview_html = _build_detail_preview(role_name, popups) if preview_html: right_parts.append(preview_html) - right_parts.append( - f'' - ) + right_html = "\n".join(right_parts) if right_parts else "" ratio = recipe.get("ratio", "6:4")