修复关系图头像缺失并调整名字与核心标识

This commit is contained in:
liuwei
2026-04-27 09:44:32 +08:00
parent b62d313690
commit f7a5096b3d

View File

@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
import base64
import json
import math
import mimetypes
import re
import xml.etree.ElementTree as ET
from datetime import datetime, timedelta
@@ -1459,6 +1461,7 @@ class ValueRankPlugin(MessagePluginInterface):
}
cm = ContactManager.get_instance()
strongest_uid = selected_nodes[0] if selected_nodes else ""
edge_svg_parts: List[str] = []
# 边数字标签只给“最重要的少数边”:
# 1. 全边标数字时,图面会被 0/1、1/0 这类噪声淹没;
@@ -1484,6 +1487,24 @@ class ValueRankPlugin(MessagePluginInterface):
units += 1.0 if ord(ch) < 128 else 1.75
return units
def _build_avatar_data_url(raw_avatar_url: str, wxid: str) -> str:
"""优先读取本地缓存头像并转 data URL减少截图时依赖远端头像可用性。"""
# 社交图渲染发生在 Playwright 截图流程中:
# 1. 如果直接给远端头像 URL链接过期、网络抖动或防盗链都可能导致头像空白
# 2. 这里优先确保头像已缓存到本地,再内联为 data URL截图时最稳定
# 3. 如果本地缓存仍失败,再回退到原始 URL尽量不影响图谱整体生成。
local_avatar_path = str(cm.ensure_head_image_cached(wxid) or "").strip()
if local_avatar_path:
try:
avatar_path = Path(local_avatar_path)
image_bytes = avatar_path.read_bytes()
mime_type = mimetypes.guess_type(str(avatar_path))[0] or "image/jpeg"
base64_str = base64.b64encode(image_bytes).decode("utf-8")
return f"data:{mime_type};base64,{base64_str}"
except Exception as exc:
self.LOG.debug(f"[{self.name}] 头像转 data url 失败: wxid={wxid}, error={exc}")
return str(raw_avatar_url or "").strip()
for edge_idx, (a, b, mention_count, score, a_to_b_count, b_to_a_count) in enumerate(selected_edges, start=1):
ax, ay = pos_map[a]
bx, by = pos_map[b]
@@ -1541,9 +1562,8 @@ class ValueRankPlugin(MessagePluginInterface):
nick = cm.get_group_name(group_id, uid) or uid
display_nick = _shorten_graph_nick(str(nick), 8)
safe_nick = html.escape(display_nick)
avatar_url = str(cm.get_head_image(uid) or "").strip()
avatar_url = _build_avatar_data_url(str(cm.get_head_image(uid) or "").strip(), uid)
ring_idx = int(node_meta_map.get(uid, {}).get("ring_idx", 0))
angle = float(node_meta_map.get(uid, {}).get("angle", 0.0))
# 使用“连接度越高环线越偏暖”的视觉策略,帮助快速识别核心节点。
ring_color = "rgba(255, 152, 0, 0.95)" if size_norm >= 0.6 else "rgba(79, 123, 201, 0.95)"
@@ -1576,6 +1596,34 @@ class ValueRankPlugin(MessagePluginInterface):
f'{html.escape(str(nick)[:1] or "?")}</text>'
)
# 最强核心节点补一个皇冠标记:
# 1. strongest_uid 取当前排序后的第一名,代表群里最强的桥梁/核心节点;
# 2. 皇冠放在头像上方,视觉上比文字提示更直观;
# 3. 使用简单 SVG 形状,避免依赖字体里的 emoji / 特殊字符。
if uid == strongest_uid:
crown_y = y - node_radius - 16.0
crown_points = [
(x - 15.0, crown_y + 18.0),
(x - 10.0, crown_y + 6.0),
(x - 2.5, crown_y + 14.0),
(x + 0.0, crown_y + 2.0),
(x + 2.5, crown_y + 14.0),
(x + 10.0, crown_y + 6.0),
(x + 15.0, crown_y + 18.0),
]
crown_points_text = " ".join([f"{px:.1f},{py:.1f}" for px, py in crown_points])
node_svg_parts.append(
f'<g>'
f'<rect x="{x - 15.0:.1f}" y="{crown_y + 18.0:.1f}" width="30.0" height="5.5" rx="2.8" ry="2.8" '
f'fill="rgba(255, 183, 3, 0.98)" stroke="rgba(168, 104, 0, 0.85)" stroke-width="1.0"></rect>'
f'<polygon points="{crown_points_text}" fill="rgba(255, 202, 40, 0.98)" '
f'stroke="rgba(168, 104, 0, 0.85)" stroke-width="1.2"></polygon>'
f'<circle cx="{x - 10.0:.1f}" cy="{crown_y + 6.0:.1f}" r="2.2" fill="rgba(255,255,255,0.96)"></circle>'
f'<circle cx="{x + 0.0:.1f}" cy="{crown_y + 2.0:.1f}" r="2.4" fill="rgba(255,255,255,0.96)"></circle>'
f'<circle cx="{x + 10.0:.1f}" cy="{crown_y + 6.0:.1f}" r="2.2" fill="rgba(255,255,255,0.96)"></circle>'
f'</g>'
)
# “连接人数”改成贴在头像上的数字徽标:
# 1. 用户反馈原先那行“连接x · 分数y”混在线条里看不清
# 2. 数字徽标直接挂在节点边缘,读图时更像“这个人连了多少人”;
@@ -1595,30 +1643,17 @@ class ValueRankPlugin(MessagePluginInterface):
f'font-size="{badge_font_size}" fill="#2F3B52" font-weight="700">{badge_text}</text>'
)
# 节点文案只保留昵称,并加半透明底板
# 1. 去掉第二行数值后,名字可以更干净地贴在节点旁边
# 2. 底板能把名字从复杂连线背景里“托”出来
# 3. 长昵称做截断,避免外围节点之间互相顶住
outward_dx = math.cos(angle)
outward_dy = math.sin(angle)
label_gap = node_radius + (14.0 if ring_idx == 0 else 18.0 + ring_idx * 2.0)
title_x = x + outward_dx * label_gap
title_y = y + outward_dy * label_gap
if abs(outward_dx) < 0.20:
anchor = "middle"
elif outward_dx > 0:
anchor = "start"
else:
anchor = "end"
# 名字固定放在头像正下方,避免沿径向排布带来的“漂移感”
# 1. 用户反馈名字位置飘忽,说明相对角度布局不利于快速扫图
# 2. 统一放在头像下方后,读图路径会稳定很多
# 3. 底板保留,继续提升名字在复杂线条背景上的可读性
title_x = x
title_y = y + node_radius + 24.0
anchor = "middle"
label_units = _estimate_label_units(display_nick)
label_width = max(44.0, min(156.0, 10.0 * label_units + 18.0))
label_height = 24.0
if anchor == "middle":
label_box_x = title_x - label_width / 2.0
elif anchor == "start":
label_box_x = title_x - 8.0
else:
label_box_x = title_x - label_width + 8.0
label_box_x = title_x - label_width / 2.0
label_box_y = title_y - 18.0
node_svg_parts.append(
f'<rect x="{label_box_x:.1f}" y="{label_box_y:.1f}" width="{label_width:.1f}" height="{label_height:.1f}" '