Skip to content

Commit 4f8749f

Browse files
committed
Switch CTA / opening banner to the article's primary product
Articles dedicated to a specific ailia component (ailia AI Voice, ailia LLM, ailia Speech, …) now route the end-of-article CTA and the opening banner to that product's Docs page instead of the generic /sdk/ root. detect_primary_product(title, slug, body): - Returns (display_name, docs_path) by checking title and slug first (e.g. ailia-ai-voiceでユーザ辞書を使用する → "ailia AI Voice", "voice/") - Falls back to body-frequency, with longest-name-first matching so "ailia AI Voice" wins over "ailia Voice" / "ailia AI" / "ailia". When detected: - CTA primary URL → docs.ailia.ai/[en/]<product_path> - CTA title → "<Product> を試す" / "Try <Product>" - Opening banner → same product's docs URL When no product is detected (or only ailia SDK), behaviour is unchanged: header stays "Try ailia SDK" / "ailia SDK を試す" and URL stays at /sdk/. Verified on the ailia-ai-voiceでユーザ辞書を使用する article: title becomes "ailia AI Voice を試す", primary CTA goes to /voice/, opening banner also goes to /voice/. ailia LLM article and ailia SDK article keep their respective targets. https://claude.ai/code/session_01JYWNoc1ay6D3BZTxaFfRjM
1 parent e3116e3 commit 4f8749f

1 file changed

Lines changed: 75 additions & 7 deletions

File tree

downloader/build_site.py

Lines changed: 75 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1214,17 +1214,69 @@ def _stash_code(m: re.Match) -> str:
12141214
return text
12151215

12161216

1217-
def article_opening_banner(tags: list, lang: dict) -> str:
1217+
def detect_primary_product(title: str, slug: str, body: str) -> tuple:
1218+
"""記事の主題となっている ailia 製品を推定する。
1219+
優先順位: title → slug → body の出現頻度。
1220+
1221+
返り値: ``(display_name, docs_path)`` のタプル。判定不能時は ``(None, None)``。
1222+
最長マッチ優先 ("ailia AI Voice" を "ailia Voice" や "ailia" より先に判定)。"""
1223+
if not (title or slug or body):
1224+
return (None, None)
1225+
title_l = (title or "").lower()
1226+
# スラグはハイフン/アンダースコア区切りなので空白に正規化
1227+
slug_l = (slug or "").lower().replace("-", " ").replace("_", " ")
1228+
body_l = (body or "").lower()
1229+
1230+
# title / slug のどちらかに含まれていれば即決
1231+
for name, path in _PRODUCT_PATHS:
1232+
n = name.lower()
1233+
if n in title_l or n in slug_l:
1234+
return (name, path)
1235+
1236+
# 本文中の最頻出を採用 (短い名前が長い名前を侵食しないよう減算する)
1237+
counts: list = []
1238+
seen_spans: list = [] # (start, end) of already-counted matches
1239+
for name, path in _PRODUCT_PATHS:
1240+
n = name.lower()
1241+
c = 0
1242+
idx = 0
1243+
while True:
1244+
i = body_l.find(n, idx)
1245+
if i == -1:
1246+
break
1247+
# 既により長い名前で計上済みの位置はスキップ
1248+
if any(s <= i < e for s, e in seen_spans):
1249+
idx = i + 1
1250+
continue
1251+
c += 1
1252+
seen_spans.append((i, i + len(n)))
1253+
idx = i + len(n)
1254+
if c > 0:
1255+
counts.append((c, name, path))
1256+
if counts:
1257+
counts.sort(key=lambda x: -x[0])
1258+
return (counts[0][1], counts[0][2])
1259+
return (None, None)
1260+
1261+
1262+
def article_opening_banner(tags: list, lang: dict, product_path: str = "") -> str:
12181263
"""記事先頭に出すカテゴリ別CTAバナー。
12191264
1265+
- 本文に出現する具体的な製品 (ailia AI Voice 等) が検出されていれば
1266+
その製品のDocsへのリンクを優先する。
12201267
- ailia-sdk タグ: ailia SDK の Docs へ
12211268
- ailia-tutorial タグ: インストール手順 (Docs/sdk) へ
12221269
- その他: 出さない
12231270
"""
12241271
docs = lang["docs_url"].rstrip("/") + "/"
1225-
# docs.ailia.ai/sdk/install/ は未公開なので、tutorial / sdk いずれも
1226-
# 現状は SDK ドキュメントトップにリンクする (将来 install/ が出来たら
1227-
# 切り戻す)。
1272+
if product_path and product_path != "sdk/":
1273+
# 製品固有のDocsへ。labelは tutorial label を流用 (誘導意図が同じ)。
1274+
return (
1275+
'<aside class="article-banner">'
1276+
f'<a href="{html.escape(docs + product_path, quote=True)}">'
1277+
f'{html.escape(lang["banner_tutorial_label"])} →</a>'
1278+
"</aside>"
1279+
)
12281280
if "ailia-tutorial" in tags:
12291281
return (
12301282
'<aside class="article-banner">'
@@ -1290,6 +1342,22 @@ def _build_language(lang: dict, source: Path, output_root: Path, md) -> list:
12901342
)
12911343
excerpt = extract_excerpt(cleaned)
12921344

1345+
# 記事の主題製品 (ailia AI Voice / ailia LLM など) を検出して
1346+
# 末尾CTAと冒頭バナーを切り替える。検出できなかった場合は
1347+
# LANGUAGES に書いた既定値 (ailia SDK) のまま。
1348+
product_name, product_path = detect_primary_product(title, slug, cleaned)
1349+
if product_path:
1350+
cta_primary_url = (
1351+
lang["docs_url"].rstrip("/") + "/" + product_path
1352+
)
1353+
if lang["code"] == "ja":
1354+
cta_title = f"{product_name} を試す"
1355+
else:
1356+
cta_title = f"Try {product_name}"
1357+
else:
1358+
cta_primary_url = lang["cta_primary_url"]
1359+
cta_title = lang["cta_title"]
1360+
12931361
body_html = md.convert(cleaned)
12941362
md.reset()
12951363

@@ -1335,11 +1403,11 @@ def _build_language(lang: dict, source: Path, output_root: Path, md) -> list:
13351403
hreflang_links=_hreflang_links(lang["code"]),
13361404
lang_switch=_lang_switch_html(lang["code"]),
13371405
site_nav=_site_nav_html(lang),
1338-
opening_banner=article_opening_banner(tags, lang),
1406+
opening_banner=article_opening_banner(tags, lang, product_path or ""),
13391407
footer_back=html.escape(lang["footer_back"]),
1340-
cta_title=html.escape(lang["cta_title"]),
1408+
cta_title=html.escape(cta_title),
13411409
cta_subtitle=html.escape(lang["cta_subtitle"]),
1342-
cta_primary_url=html.escape(lang["cta_primary_url"], quote=True),
1410+
cta_primary_url=html.escape(cta_primary_url, quote=True),
13431411
cta_primary_label=html.escape(lang["cta_primary_label"]),
13441412
cta_secondary_url=html.escape(lang["contact_url"], quote=True),
13451413
cta_secondary_label=html.escape(lang["cta_secondary_label"]),

0 commit comments

Comments
 (0)