diff --git a/plugins/message_summary/main.py b/plugins/message_summary/main.py
index dc118d3..e1807f3 100644
--- a/plugins/message_summary/main.py
+++ b/plugins/message_summary/main.py
@@ -528,11 +528,19 @@ class MessageSummaryPlugin(MessagePluginInterface):
if not summary or not summary.strip():
return summary
+ pairs = [
+ ("总", int(stats.get("total_count") or 0)),
+ ("人数", int(stats.get("participant_count") or 0)),
+ ("文本", int(stats.get("text_count") or 0)),
+ ("图片", int(stats.get("image_count") or 0)),
+ ("视频", int(stats.get("video_count") or 0)),
+ ("链接", int(stats.get("link_count") or 0)),
+ ("表情", int(stats.get("emoji_count") or 0)),
+ ]
+ stats_line = " · ".join([f"**{label}** {value}" for label, value in pairs])
section_lines = [
"## 群概览",
- f"- 📊 总:{int(stats.get('total_count') or 0)} 👥 人:{int(stats.get('participant_count') or 0)}\n"
- f"- 💬 文:{int(stats.get('text_count') or 0)} 🖼 图:{int(stats.get('image_count') or 0)} 🎬 视:{int(stats.get('video_count') or 0)}\n"
- f"- 🔗 链:{int(stats.get('link_count') or 0)} 😄 表:{int(stats.get('emoji_count') or 0)}"
+ stats_line,
]
section = "\n".join(section_lines)
return f"{section}\n\n{summary.strip()}"
diff --git a/utils/markdown_to_image.py b/utils/markdown_to_image.py
index 8f59952..1d1ca2e 100644
--- a/utils/markdown_to_image.py
+++ b/utils/markdown_to_image.py
@@ -15,6 +15,43 @@ except ImportError:
markdown = None
META_KEYWORDS = ["群", "群名", "时间", "日期", "成员", "消息", "统计", "总结", "来源", "生成", "记录"]
+STAT_PILL_CLASSES = {
+ "总": "total",
+ "人数": "people",
+ "文本": "text",
+ "图片": "image",
+ "视频": "video",
+ "链接": "link",
+ "表情": "emoji",
+}
+
+
+def _extract_stats_pills_from_markdown(md_content: str) -> str:
+ text = str(md_content or "")
+ pattern = re.compile(
+ r"(^##\s+群概览\s*\n)([^\n]+)(?=\n(?:\n|##\s|###\s|$))",
+ re.M,
+ )
+
+ def replace(match):
+ stats_line = match.group(2).strip()
+ parts = [part.strip() for part in stats_line.split("·") if part.strip()]
+ pills = []
+ for part in parts:
+ item_match = re.match(r"(?:\*\*)?([^*\s]+)(?:\*\*)?\s+(\d+)", part)
+ if not item_match:
+ continue
+ label = item_match.group(1).strip()
+ value = item_match.group(2).strip()
+ kind = STAT_PILL_CLASSES.get(label, "default")
+ pills.append(
+ f'{label}{value}'
+ )
+ if not pills:
+ return match.group(0)
+ return match.group(1) + f'
{"".join(pills)}
'
+
+ return pattern.sub(replace, text, count=1)
def _simple_markdown_to_html(md_content: str) -> str:
lines = str(md_content or "").splitlines()
@@ -58,6 +95,11 @@ def _simple_markdown_to_html(md_content: str) -> str:
close_ul()
html_parts.append(f"{stripped[4:].strip()}
")
continue
+ if stripped.startswith(""):
+ flush_paragraph()
+ close_ul()
+ html_parts.append(stripped)
+ continue
if stripped.startswith("- "):
flush_paragraph()
if not in_ul:
@@ -165,6 +207,7 @@ def _split_hero(html_body: str):
async def md_str_to_html_content(md_content):
+ md_content = _extract_stats_pills_from_markdown(md_content)
if markdown is not None:
html_body = markdown.markdown(md_content, extensions=['extra', 'codehilite'])
else:
@@ -354,6 +397,48 @@ async def md_str_to_html_content(md_content):
border-radius: 14px;
color: #355468;
}
+ .stats-pills {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+ margin: 12px 0 8px;
+ }
+ .stats-pill {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 7px 12px;
+ border-radius: 999px;
+ font-size: 0.92em;
+ line-height: 1;
+ border: 1px solid rgba(148,163,184,0.16);
+ background: linear-gradient(180deg, rgba(255,255,255,0.96), rgba(248,250,252,0.92));
+ color: #334155;
+ box-shadow: 0 8px 18px rgba(15,23,42,0.05);
+ }
+ .stats-pill-label {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 4px 8px;
+ border-radius: 999px;
+ font-size: 0.82em;
+ font-weight: 700;
+ color: #ffffff;
+ background: linear-gradient(135deg, #64748b, #475569);
+ }
+ .stats-pill-value {
+ font-weight: 800;
+ color: #1e293b;
+ min-width: 20px;
+ }
+ .stats-pill-total .stats-pill-label { background: linear-gradient(135deg, #3b82f6, #1d4ed8); }
+ .stats-pill-people .stats-pill-label { background: linear-gradient(135deg, #0f766e, #14b8a6); }
+ .stats-pill-text .stats-pill-label { background: linear-gradient(135deg, #8b5cf6, #7c3aed); }
+ .stats-pill-image .stats-pill-label { background: linear-gradient(135deg, #ec4899, #db2777); }
+ .stats-pill-video .stats-pill-label { background: linear-gradient(135deg, #f97316, #ea580c); }
+ .stats-pill-link .stats-pill-label { background: linear-gradient(135deg, #22c55e, #16a34a); }
+ .stats-pill-emoji .stats-pill-label { background: linear-gradient(135deg, #eab308, #ca8a04); }
hr { border: none; height: 1px; background: linear-gradient(90deg, transparent, rgba(148,163,184,0.35), transparent); margin: 26px 0; }
a { color: var(--primary); text-decoration: none; border-bottom: 1px dashed rgba(109,94,252,0.35); }
.signature { margin-top: 34px; text-align: right; color: #73849c; font-size: 0.95em; font-style: italic; }