Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""generate_thumbnail node — gpt-image-2 magazine-style 2:3 thumbnail.

source post 1장 + 한글 title overlay → OpenAI gpt-image-1 edit → R2 업로드.
nano-banana 는 한글 text rendering 약해서 이번 노드에서는 OpenAI 사용 (#429).
source post 1장 + English title overlay → OpenAI gpt-image-1 edit → R2 업로드.
nano-banana 는 text rendering 약해서 이번 노드에서는 OpenAI 사용 (#429).

흐름:
1. layout 의 첫 curation_card image_url 다운로드
Expand Down Expand Up @@ -34,26 +34,28 @@


def _build_prompt(title: str, subtitle: Optional[str]) -> str: # noqa: ARG001
return f"""Transform this photograph into a Korean fashion magazine Instagram-style thumbnail
return f"""Transform this photograph into an English fashion magazine Instagram-style thumbnail
(2:3 portrait, 1024x1536).

KEEP the subject (person, pose, outfit) recognizable from the source photo.

ADD design treatment over the photograph:

1. KOREAN TITLE TEXT OVERLAY at bottom-left:
1. ENGLISH TITLE TEXT OVERLAY at bottom-left:
"{title}"

Typography:
- Bold sans-serif Korean (Pretendard Bold / Noto Sans CJK Bold style)
- Bold sans-serif English (Inter Bold / Helvetica Neue Bold / Söhne Bold style)
- WHITE color, large readable thumbnail size
- Break into 2-3 lines at natural Korean phrase boundaries (commas, particles)
- Break into 2-3 lines at natural English phrase boundaries
(commas, prepositions, conjunctions)
- Use Title Case or ALL CAPS — pick one and stay consistent
- Position in lower-left third with proper margin from image edge

2. "DECODED MAG" wordmark in TOP-RIGHT corner — REQUIRED:
- Color: bright lime green (#eafd67) — the brand magazine accent color
- Bold sans-serif uppercase (modern editorial typography — think Pretendard /
Inter / Helvetica Bold)
- Bold sans-serif uppercase (modern editorial typography — Inter Bold /
Helvetica Neue Bold)
- Small but readable size — magazine masthead style
- Position: top-right with proper margin (matches title margin on bottom-left)
- Render text PERFECTLY — exactly "DECODED MAG" (uppercase, with single space)
Expand All @@ -63,13 +65,13 @@ def _build_prompt(title: str, subtitle: Optional[str]) -> str: # noqa: ARG001
4. If text legibility needs help, add a subtle dark gradient ONLY in the text area (lower-left).

CRITICAL:
- Render the Korean text PERFECTLY — no typos, no character substitutions
- Render the English text PERFECTLY — no typos, no character substitutions
- 2:3 portrait (1024x1536) strictly
- Subject must remain visible behind/around text overlay

Style reference: 053 Magazine, W Korea, Vogue Korea Instagram covers.
Style reference: i-D, Dazed, Highsnobiety, AnOther Magazine Instagram covers.

Output: the photograph with Korean text overlay applied."""
Output: the photograph with English text overlay applied."""


def _pick_source_url(layout: MagazineLayout) -> Optional[str]:
Expand Down
51 changes: 29 additions & 22 deletions packages/ai-server/src/editorial_article/prompts.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,38 +36,45 @@ def build_compose_layout_prompt(ctx: RecommendationContext) -> str:
posts_json = json.dumps(posts_payload, ensure_ascii=False, indent=2)
keywords_str = ", ".join(ctx.keywords) if ctx.keywords else ""

return f"""너는 Decoded 매거진의 시니어 에디터다.
아래 angle source posts 를 바탕으로 **Hypebeast TAGGED 스타일** 의
멀티 소스 매거진 기사 layout 을 작성해라.
return f"""You are the senior editor of Decoded magazine.
Based on the angle and source posts below, write a multi-source magazine
article layout in the **Hypebeast TAGGED style** — but in English.

[Angle]
title: {ctx.angle_title}
description: {ctx.angle_description or ''}
rationale: {ctx.rationale or ''}
keywords: {keywords_str}

[Source posts (post_id 와 솔루션)]
[Source posts (post_id and solutions)]
{posts_json}

요구사항:
- title: angle_title 을 다듬은 매거진 제목 (한국어, TAGGED 톤).
- subtitle: 한 문장 부제.
- hero_image_url: null. (#429 — 매거진 본문에 hero banner 안 씀, OG 메타 등은
thumbnail_url 사용. LLM 은 hero_image_url 채우지 마라.)
- sections: 다음 순서로 구성. **hero 섹션 만들지 마라** — title/subtitle 은 layout
의 최상위 필드에서 이미 표시됨.
1. intro (200-300자 도입 카피, 이 angle 이 왜 지금 의미있는지)
2. curation_card N개 — source post 마다 1개. body 에 100-200자 카피, post_id /
image_url / solutions (그 post 의 솔루션 카드들) 포함.
3. (선택) spotlight 1개 — 가장 강조하고 싶은 솔루션 / 브랜드.
4. closing (100-150자 마무리 카피)
- 모든 post_id / solution_id 는 입력에 등장한 것만 사용. 새 id 만들지 마라.
- solutions 배열에는 input solution 의 **original_url / affiliate_url 그대로
복사** (있으면). 사용자가 상품 페이지로 바로 갈 수 있어야 함. URL 변형 X.
- "Hypebeast" / "Tagged" 라는 단어는 출력에 절대 포함 금지. Decoded 로 표기.
- 카피는 한국어, 자연스럽고 트렌디한 매거진 톤.
Requirements:
- title: a refined English magazine headline derived from angle_title (TAGGED
editorial tone — punchy, trend-aware, no clickbait).
- subtitle: one English sentence.
- hero_image_url: null. (#429 — the magazine body does not use a hero banner;
OG/share use thumbnail_url. Do NOT fill hero_image_url.)
- sections: in this order. **Do NOT create a hero section** — title/subtitle
are already rendered from the layout's top-level fields.
1. intro (200-400 chars of opening copy in English — why this angle matters
right now)
2. curation_card × N — one per source post. body is 100-250 chars of English
copy. Include post_id / image_url / solutions (the solution cards for
that post).
3. (optional) spotlight × 1 — the single solution / brand most worth
highlighting.
4. closing (100-200 chars of English closing copy)
- Use only post_id / solution_id values present in the input. Never invent ids.
- In the solutions array, copy the input solution's **original_url /
affiliate_url verbatim** when present, so the reader can jump straight to
the product page. Do not modify URLs.
- The words "Hypebeast" / "Tagged" must NEVER appear in output. Brand as
Decoded.
- All copy in English. Natural, on-trend magazine tone — confident, specific,
no filler.

JSON 출력만. MagazineLayout 스키마:
Output JSON only. MagazineLayout schema:
{{"schema_version": "1.0", "title": "...", "subtitle": "...",
"hero_image_url": "...",
"sections": [{{"type": "intro|curation_card|spotlight|closing",
Expand Down
31 changes: 31 additions & 0 deletions packages/web/app/admin/editorial/magazine/drafts/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,37 @@ function ArticleDetailContent({ id }: { id: string }) {
</div>

<aside className="space-y-4">
<section className="rounded-lg border border-border p-4 space-y-3">
<h2 className="text-sm font-semibold text-foreground">
Thumbnail preview
</h2>
<div className="relative w-full aspect-[4/5] overflow-hidden rounded-md border border-border bg-muted">
{article.thumbnail_url ? (
// eslint-disable-next-line @next/next/no-img-element
<img
src={article.thumbnail_url}
alt={`${article.title || "Untitled"} thumbnail`}
className="absolute inset-0 h-full w-full object-cover"
/>
) : (
<div className="absolute inset-0 flex items-center justify-center text-xs text-muted-foreground text-center px-3">
Not generated yet
</div>
)}
</div>
{article.thumbnail_url && (
<a
href={article.thumbnail_url}
target="_blank"
rel="noopener noreferrer"
className="block text-[11px] text-muted-foreground hover:text-foreground truncate"
title={article.thumbnail_url}
>
Open original ↗
</a>
)}
</section>

<ChatPanel articleId={article.id} />

<section className="rounded-lg border border-border p-4 space-y-3">
Expand Down
Loading