feat: enrich douyu daily summary cards
This commit is contained in:
@@ -1284,7 +1284,7 @@ class DouyuPlugin(MessagePluginInterface):
|
||||
user_prompt = (
|
||||
"请输出一段适合放在日报图片上半部分的弹幕总结,要求:\n"
|
||||
"1. 先用 1 段总述直播氛围与主线。\n"
|
||||
"2. 再用 3-5 条要点总结观众关注点、情绪变化、反复出现的梗。\n"
|
||||
"2. 再用 5 条要点总结观众关注点、情绪变化、反复出现的梗、节奏变化和额外反馈,每条只写一句。\n"
|
||||
"3. 语言像运营复盘,简洁自然。\n"
|
||||
"4. 不要写标题,不要写“根据数据”。\n\n"
|
||||
f"主播:{meta.get('nickname') or meta.get('room_name') or meta.get('room_id')}\n"
|
||||
|
||||
@@ -39,12 +39,90 @@ def _split_summary_blocks(danmu_summary: str) -> tuple[str, List[str]]:
|
||||
return lead, insight_items
|
||||
|
||||
|
||||
def _normalize_summary_bullets(payload: Dict[str, Any], items: List[str], target_count: int = 5) -> List[str]:
|
||||
normalized = [str(item or "").strip() for item in items if str(item or "").strip()]
|
||||
if len(normalized) >= target_count:
|
||||
return normalized[:target_count]
|
||||
|
||||
top_terms = [str(item.get("term") or "").strip() for item in (payload.get("top_terms", []) or []) if str(item.get("term") or "").strip()]
|
||||
merged_templates = [str(item.get("text") or "").strip() for item in (payload.get("merged_templates", []) or []) if str(item.get("text") or "").strip()]
|
||||
peak_buckets = payload.get("peak_buckets", []) or []
|
||||
representative_messages = payload.get("representative_messages", []) or []
|
||||
|
||||
supplements: List[str] = []
|
||||
if top_terms:
|
||||
supplements.append(f"讨论焦点比较集中,弹幕反复围绕 {'、'.join(top_terms[:5])} 展开。")
|
||||
if merged_templates:
|
||||
sample_templates = ";".join(text[:24] for text in merged_templates[:3])
|
||||
supplements.append(f"复读和共识梗比较强,重复内容主要集中在 {sample_templates}。")
|
||||
if peak_buckets:
|
||||
top_bucket = peak_buckets[0]
|
||||
bucket_terms = [str(term.get('term') or '').strip() for term in (top_bucket.get("top_terms", []) or []) if str(term.get('term') or '').strip()]
|
||||
if bucket_terms:
|
||||
supplements.append(
|
||||
f"高峰时段出现在 {str(top_bucket.get('start_time') or '')[-8:-3]} 前后,话题明显偏向 {'、'.join(bucket_terms[:4])}。"
|
||||
)
|
||||
if representative_messages:
|
||||
supplements.append("代表性发言里既有操作反馈,也有玩梗调侃和情绪宣泄,互动意愿比较强。")
|
||||
|
||||
existing = set(normalized)
|
||||
for item in supplements:
|
||||
if item not in existing:
|
||||
normalized.append(item)
|
||||
existing.add(item)
|
||||
if len(normalized) >= target_count:
|
||||
break
|
||||
return normalized[:target_count]
|
||||
|
||||
|
||||
def _build_template_items(payload: Dict[str, Any], limit: int = 8) -> List[str]:
|
||||
items: List[str] = []
|
||||
seen = set()
|
||||
|
||||
def push(text: str, suffix: str = "") -> None:
|
||||
value = str(text or "").strip()
|
||||
if not value:
|
||||
return
|
||||
normalized_key = value
|
||||
if normalized_key in seen:
|
||||
return
|
||||
seen.add(normalized_key)
|
||||
items.append(f"{value}{suffix}".strip())
|
||||
|
||||
for item in (payload.get("merged_templates", []) or [])[:6]:
|
||||
text = str(item.get("text") or "").strip()
|
||||
count = int(item.get("count", 0) or 0)
|
||||
if text:
|
||||
push(text[:72], f"({count}次)")
|
||||
|
||||
for item in (payload.get("repeated_messages", []) or [])[:6]:
|
||||
text = str(item.get("text") or "").strip()
|
||||
count = int(item.get("count", 0) or 0)
|
||||
if text:
|
||||
push(text[:72], f"({count}次)")
|
||||
|
||||
for item in (payload.get("burst_terms", []) or [])[:6]:
|
||||
text = str(item.get("text") or "").strip()
|
||||
count = int(item.get("count", 0) or 0)
|
||||
if text:
|
||||
push(text[:36], f"(爆发 {count} 次)")
|
||||
|
||||
for item in (payload.get("top_terms", []) or [])[:6]:
|
||||
term = str(item.get("term") or "").strip()
|
||||
count = int(item.get("count", 0) or 0)
|
||||
if term:
|
||||
push(term, f"({count}次提及)")
|
||||
|
||||
return items[:limit]
|
||||
|
||||
|
||||
def _render_insight_cards(items: List[str]) -> str:
|
||||
labels = ["主线", "情绪", "梗点", "节奏", "反馈", "补充"]
|
||||
blocks = []
|
||||
for idx, item in enumerate(items[:6]):
|
||||
extra_class = " full-span" if len(items[:6]) % 2 == 1 and idx == len(items[:6]) - 1 else ""
|
||||
blocks.append(
|
||||
'<div class="insight-card">'
|
||||
f'<div class="insight-card{extra_class}">'
|
||||
f'<div class="insight-kicker">{_escape(labels[idx] if idx < len(labels) else "观察")}</div>'
|
||||
f'<div class="insight-text">{_escape(item)}</div>'
|
||||
"</div>"
|
||||
@@ -103,12 +181,7 @@ def render_daily_report_html(
|
||||
_render_metric_card("30+等级用户", operator.get("high_room_level_user_count", 0), "高等级老观众"),
|
||||
])
|
||||
|
||||
merged_templates = payload.get("merged_templates", []) or []
|
||||
template_items = [
|
||||
f"{str(item.get('text') or '').strip()[:72]}({int(item.get('count', 0) or 0)}次)"
|
||||
for item in merged_templates[:5]
|
||||
if str(item.get("text") or "").strip()
|
||||
]
|
||||
template_items = _build_template_items(payload)
|
||||
top_active_users = payload.get("operator_metrics", {}).get("top_active_users", []) or []
|
||||
active_user_items = []
|
||||
for item in top_active_users[:10]:
|
||||
@@ -121,6 +194,7 @@ def render_daily_report_html(
|
||||
active_user_items.append(f"{nickname} | {message_count}条")
|
||||
|
||||
lead_summary, danmu_bullets = _split_summary_blocks(danmu_summary)
|
||||
danmu_bullets = _normalize_summary_bullets(payload, danmu_bullets, target_count=5)
|
||||
|
||||
html_doc = f"""<html>
|
||||
<head>
|
||||
@@ -314,6 +388,9 @@ def render_daily_report_html(
|
||||
border: 1px solid rgba(125, 145, 186, 0.14);
|
||||
min-height: 110px;
|
||||
}}
|
||||
.insight-card.full-span {{
|
||||
grid-column: 1 / -1;
|
||||
}}
|
||||
.insight-kicker {{
|
||||
color: #2b59ff;
|
||||
font-size: 12px;
|
||||
|
||||
Reference in New Issue
Block a user