Add Type B slide pipeline and recipe rendering updates
This commit is contained in:
@@ -30,33 +30,21 @@ KEI_PROMPT = (
|
||||
"## 3단계: 슬라이드 스토리라인 설계\n"
|
||||
"핵심 메시지를 전달하기 위한 **흐름**을 설계해줘.\n"
|
||||
"각 꼭지에 purpose를 부여하고, topics 배열에 기록.\n\n"
|
||||
"## 4단계: 레이아웃 유형 선택 + 페이지 구조 판단\n"
|
||||
"먼저 콘텐츠에 맞는 **레이아웃 유형**을 선택하라:\n\n"
|
||||
"### 유형 A: 배경 + 본심 + 첨부(sidebar) + 결론\n"
|
||||
"- 참조자료(용어 정의, 부록 등)가 **별도로 존재**하는 콘텐츠\n"
|
||||
"- 좌측 body(배경+본심) + 우측 sidebar(첨부) + 하단 결론\n"
|
||||
"- page_structure 키: 배경, 본심, 첨부, 결론\n\n"
|
||||
"### 유형 B: 본심1(상단) + 본심2(하단 2분할) + 결론\n"
|
||||
"- 참조자료 없이 **본문 흐름만**으로 구성되는 콘텐츠\n"
|
||||
"- 배경/첨부가 없거나 억지로 만들어야 하면 이 유형 선택\n"
|
||||
"- 상단: 핵심 내용 전체폭 (이미지가 있으면 좌텍스트+우이미지 나란히)\n"
|
||||
"- 하단: 세부 내용 2분할 (좌/우)\n"
|
||||
"- page_structure 키: 자유 (예: 핵심목표, 프로세스변화, 기대효과, 결론)\n"
|
||||
"- 결론 키는 반드시 '결론'\n\n"
|
||||
"선택한 유형을 **layout_template** 필드에 'A' 또는 'B'로 기록하라.\n\n"
|
||||
"### 역할별 규칙 (유형 A)\n"
|
||||
"- **본심**: 이 페이지가 말하려는 핵심. 가장 큰 공간.\n"
|
||||
"- **배경**: 본심을 이해하기 위한 도입. 간결하게.\n"
|
||||
"- **첨부**: 본심을 보조하는 참조 정보. sidebar 배치. role: 'reference'.\n"
|
||||
"- **결론**: 핵심 한 줄. footer.\n\n"
|
||||
"### 역할별 규칙 (유형 B)\n"
|
||||
"- 상단 역할: 핵심 내용. 전체폭. zone: 'top'\n"
|
||||
"- 하단 좌측: zone: 'bottom_left'\n"
|
||||
"- 하단 우측: zone: 'bottom_right'\n"
|
||||
"- 결론: zone: 'footer'\n\n"
|
||||
"각 역할에 해당하는 topic_ids와 **공간 비중(weight, 합계 1.0)**을 결정하라.\n"
|
||||
"**콘텐츠에 따라 비중은 매번 달라진다. 고정값이 아니다.**\n"
|
||||
"page_structure 필드에 기록.\n\n"
|
||||
"## 4단계: 꼭지별 성격 판단\n"
|
||||
"각 꼭지에 대해 다음을 판단하라:\n\n"
|
||||
"### sidebar 판단\n"
|
||||
"- 이 꼭지의 내용이 **본문과 독립된 참조 정보**(용어 정의, 개념 비교, 참조 테이블)인가?\n"
|
||||
"- 독립 참조 → role: 'reference' (sidebar 후보)\n"
|
||||
"- 본문 흐름의 일부 → role: 'flow'\n\n"
|
||||
"### 팝업 판단\n"
|
||||
"- <details> 안에 있는 콘텐츠 → 팝업 처리 대상\n"
|
||||
"- 너무 세부적인 내용 → 팝업으로 분리 가능\n\n"
|
||||
"### 핵심요약\n"
|
||||
"- :::note[핵심 요약] 등의 결론 텍스트가 있으면 **conclusion_text** 필드에 원본 그대로 기록\n"
|
||||
"- conclusion_text는 슬라이드 하단 footer에 자동 배치됨\n\n"
|
||||
"**주의: page_structure, zone, 영역 배치는 판단하지 마라.**\n"
|
||||
"**영역과 zone은 코드가 블록 매칭을 통해 결정한다.**\n"
|
||||
"**너는 꼭지 추출 + 각 꼭지의 성격(reference/flow, 팝업 여부)만 판단하라.**\n\n"
|
||||
"## 원본 텍스트 보존 원칙 (절대 규칙)\n"
|
||||
"- **제목(##, ###)은 원본 그대로 사용하라. 절대 바꾸지 마라.**\n"
|
||||
" 원본이 '## 1. DX의 궁극적 목표'이면 꼭지 제목도 'DX의 궁극적 목표'.\n"
|
||||
@@ -70,61 +58,76 @@ KEI_PROMPT = (
|
||||
"## 배치 규칙\n"
|
||||
"- 참조 정보(용어 정의 등)는 role: 'reference'로 표시 → 사이드바 배치\n"
|
||||
"- 본문 흐름은 role: 'flow' → 메인 영역 배치\n"
|
||||
"- 결론은 layer: 'conclusion' → 하단 배치\n"
|
||||
"- 결론/핵심요약은 conclusion_text 필드에 기록. page_structure에 넣지 마라.\n"
|
||||
"- detail_target: true는 정말로 별도로 봐야 하는 상세 데이터에만 사용\n"
|
||||
"- 이미지/표가 있으면 images[], tables[]에 기록\n"
|
||||
"- 1페이지 적정 꼭지: 5개. 분량 적으면 1페이지로.\n"
|
||||
"- **슬라이드 제목(title)과 첫 번째 꼭지 제목은 달라야 한다.** 슬라이드 제목은 전체 주제, 꼭지 제목은 해당 위치의 구체적 내용.\n\n"
|
||||
"## 출력 형식 (JSON만)\n"
|
||||
"layout_template에 따라 page_structure가 달라진다.\n\n"
|
||||
"유형 A 예시:\n"
|
||||
"**page_structure는 출력하지 마라. 영역/zone 배치는 코드가 결정한다.**\n\n"
|
||||
"```json\n"
|
||||
'{"title": "제목", '
|
||||
'{"title": "슬라이드 제목 (MDX title 또는 전체 주제)", '
|
||||
'"core_message": "핵심 메시지", '
|
||||
'"conclusion_text": "핵심 요약 원본 텍스트 (:::note 등에서 추출. 없으면 빈 문자열)", '
|
||||
'"total_pages": 1, '
|
||||
'"layout_template": "A", '
|
||||
'"page_structure": {'
|
||||
'"본심": {"topic_ids": [2, 3], "weight": 0.60}, '
|
||||
'"배경": {"topic_ids": [1], "weight": 0.15}, '
|
||||
'"첨부": {"topic_ids": [4], "weight": 0.15}, '
|
||||
'"결론": {"topic_ids": [5], "weight": 0.10}}, '
|
||||
'"topics": ['
|
||||
'{"id": 1, "title": "꼭지 제목", "summary": "요약", '
|
||||
'{"id": 1, "title": "꼭지 제목 (원본 그대로)", "summary": "요약", '
|
||||
'"purpose": "문제제기|근거사례|핵심전달|용어정의|결론강조|구조시각화", '
|
||||
'"source_hint": "원본에서 이 위치에 가져올 텍스트 범위 설명", '
|
||||
'"source_hint": "원본에서 이 꼭지에 해당하는 텍스트 범위 설명", '
|
||||
'"layer": "intro|core|supporting|conclusion", '
|
||||
'"role": "flow|reference", '
|
||||
'"section_title": "sidebar에 표시할 섹션 제목 (reference일 때만. 예: 용어 정의, 참고 자료)", '
|
||||
'"emphasis": true, "direction": "vertical|horizontal|flexible", '
|
||||
'"content_type": "text|image|table|mixed", '
|
||||
'"detail_target": false, "page": 1}], '
|
||||
'"images": [{"topic_id": 1, "role": "key|supporting", "has_text": false, "description": "이미지 설명"}], '
|
||||
'"tables": [{"topic_id": 2, "rows": 5, "cols": 3, "fits_single_page": true, "description": "표 설명"}]}\n'
|
||||
"```\n\n"
|
||||
"유형 B 예시:\n"
|
||||
"```json\n"
|
||||
'{"title": "제목", '
|
||||
'"core_message": "핵심 메시지", '
|
||||
'"total_pages": 1, '
|
||||
'"layout_template": "B", '
|
||||
'"page_structure": {'
|
||||
'"핵심목표": {"zone": "top", "topic_ids": [1], "weight": 0.35}, '
|
||||
'"프로세스변화": {"zone": "bottom_left", "topic_ids": [2], "weight": 0.25}, '
|
||||
'"기대효과": {"zone": "bottom_right", "topic_ids": [3], "weight": 0.25}, '
|
||||
'"결론": {"zone": "footer", "topic_ids": [4], "weight": 0.15}}, '
|
||||
'"topics": [...],'
|
||||
'"images": [...]}\n'
|
||||
"```\n\n"
|
||||
"## 콘텐츠:\n"
|
||||
)
|
||||
|
||||
|
||||
def _detect_structure_hints(content: str) -> str:
|
||||
"""MDX 구조에서 유형 판단 힌트를 자동 감지."""
|
||||
hints = []
|
||||
content_lower = content.lower()
|
||||
|
||||
# 용어 정의 섹션 감지
|
||||
if re.search(r'##\s*\d*\.?\s*용어\s*정의', content):
|
||||
hints.append("[구조 힌트] '용어 정의' 섹션 감지 → 유형 A 후보")
|
||||
if re.search(r'##\s*\d*\.?\s*개념\s*비교', content):
|
||||
hints.append("[구조 힌트] '개념 비교' 섹션 감지 → 유형 A 후보")
|
||||
|
||||
# sidebar 마크다운 감지
|
||||
if 'sidebar:' in content[:200]:
|
||||
pass # frontmatter의 sidebar는 Starlight 설정이므로 무시
|
||||
|
||||
# <details> 감지
|
||||
if '<details>' in content:
|
||||
hints.append("[구조 힌트] <details> 참고 사례 감지 → 팝업 처리 대상 (유형 선택과 무관)")
|
||||
|
||||
# 표 감지
|
||||
if '|' in content and '---' in content:
|
||||
hints.append("[구조 힌트] 표(테이블) 감지 → 비교 구조")
|
||||
|
||||
# 이미지 감지
|
||||
if re.search(r'!\[.*?\]\(.*?\)', content):
|
||||
hints.append("[구조 힌트] 이미지 감지 → 이미지 배치 필요")
|
||||
|
||||
# A 후보 힌트가 없으면 B 유력
|
||||
if not any("유형 A 후보" in h for h in hints):
|
||||
hints.append("[구조 힌트] 독립 참조 섹션 없음 → 유형 B 유력")
|
||||
|
||||
return "\n".join(hints) + "\n\n"
|
||||
|
||||
|
||||
async def classify_content(content: str) -> dict[str, Any] | None:
|
||||
"""1단계: Kei API를 통해 꼭지를 추출하고 분석한다.
|
||||
|
||||
Kei API만 사용. fallback 없음. 실패 시 None → pipeline에서 에러.
|
||||
"""
|
||||
result = await _call_kei_api(content)
|
||||
# MDX 구조 힌트를 content 앞에 추가
|
||||
hints = _detect_structure_hints(content)
|
||||
result = await _call_kei_api(hints + content)
|
||||
if result:
|
||||
logger.info(
|
||||
f"[Kei API] 꼭지 추출 완료: {result.get('title', '')}, "
|
||||
|
||||
Reference in New Issue
Block a user