Initial commit: Kei Design Agent
콘텐츠를 시각적으로 구조화된 슬라이드 HTML로 변환하는 독립 에이전트. 아키텍처 (4단계 파이프라인): 1. Kei 실장 (Opus) — 콘텐츠 유형 분류 + 블록 배치 2. 디자인 팀장 (Sonnet) — 레이아웃 컨셉 (블록 배치 + 페이지 수) 3. 텍스트 편집자 (Sonnet) — 슬롯 텍스트 정리 (핵심 유지) 4. CSS Grid 렌더러 — HTML 조립 블록 템플릿 7종: comparison, card-grid, relationship, process, quote-block, conclusion-bar, comparison-table 기술 스택: FastAPI + Anthropic API + Jinja2 + CSS Grid Pretendard Variable 한국어 폰트 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
71
src/pipeline.py
Normal file
71
src/pipeline.py
Normal file
@@ -0,0 +1,71 @@
|
||||
"""DA-14: 전체 파이프라인 (3단계).
|
||||
|
||||
콘텐츠 입력 → Opus 분류 → 디자인 팀장 컨셉 → 텍스트 편집자 정리 → 렌더러 조립 → HTML 출력.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any, AsyncIterator
|
||||
|
||||
from src.kei_client import classify_content, manual_classify
|
||||
from src.design_director import create_layout_concept, _fallback_single_page
|
||||
from src.content_editor import fill_content
|
||||
from src.renderer import render_slide
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def generate_slide(
|
||||
content: str,
|
||||
manual_layout: dict[str, Any] | None = None,
|
||||
) -> AsyncIterator[dict[str, str]]:
|
||||
"""콘텐츠를 슬라이드 HTML로 변환하는 전체 파이프라인.
|
||||
|
||||
Args:
|
||||
content: 원본 텍스트 콘텐츠
|
||||
manual_layout: 수동 레이아웃 명세 (Opus 대신 사용)
|
||||
|
||||
Yields:
|
||||
SSE 이벤트:
|
||||
{"event": "progress", "data": "단계 설명"}
|
||||
{"event": "result", "data": "완성 HTML"}
|
||||
{"event": "error", "data": "에러 메시지"}
|
||||
"""
|
||||
try:
|
||||
# 1단계: Kei 실장 (Opus) — 콘텐츠 분류
|
||||
yield {"event": "progress", "data": "1/4 Kei 실장이 콘텐츠를 분석 중..."}
|
||||
|
||||
if manual_layout:
|
||||
classification = manual_layout
|
||||
else:
|
||||
classification = await classify_content(content)
|
||||
if classification is None:
|
||||
classification = manual_classify(content)
|
||||
|
||||
logger.info(f"분류 완료: {len(classification.get('blocks', []))}개 블록")
|
||||
|
||||
# 2단계: 디자인 팀장 — 레이아웃 컨셉
|
||||
yield {"event": "progress", "data": "2/4 디자인 팀장이 레이아웃을 설계 중..."}
|
||||
|
||||
layout_concept = await create_layout_concept(content, classification)
|
||||
|
||||
total_pages = len(layout_concept.get("pages", []))
|
||||
total_blocks = sum(len(p.get("blocks", [])) for p in layout_concept.get("pages", []))
|
||||
logger.info(f"레이아웃 컨셉: {total_pages}페이지, {total_blocks}개 블록")
|
||||
|
||||
# 3단계: 텍스트 편집자 (Kei 역할) — 슬롯 텍스트 정리
|
||||
yield {"event": "progress", "data": "3/4 텍스트 편집자가 핵심을 정리 중..."}
|
||||
|
||||
layout_concept = await fill_content(content, layout_concept)
|
||||
|
||||
# 4단계: 실무자 — HTML 렌더링
|
||||
yield {"event": "progress", "data": "4/4 슬라이드를 조립 중..."}
|
||||
|
||||
html = render_slide(layout_concept)
|
||||
|
||||
yield {"event": "result", "data": html}
|
||||
logger.info(f"슬라이드 생성 완료: {total_pages}페이지")
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"파이프라인 오류: {e}")
|
||||
yield {"event": "error", "data": str(e)}
|
||||
Reference in New Issue
Block a user