fix(HatsuneMiku): 完善

This commit is contained in:
SXP-Simon
2026-03-28 21:18:50 +08:00
parent ae96ea9c93
commit 4200631f17
12 changed files with 579 additions and 343 deletions
+3
View File
@@ -28,3 +28,6 @@ scripts/output_all/debug_scrapbook_test_group_*.pdf
scripts/output_all/debug_simple_test_group_*.pdf
scripts/output_all/debug_spring_festival_test_group_*.pdf
scripts/data/avatar/cache.db
debug_miku.html
scripts/output_all/debug_HatsuneMiku_test_group_*.pdf
scripts/output/mock_report_test_group_mock_*.pdf
+10 -4
View File
@@ -16,6 +16,10 @@
<a href="https://t.me/AstrBotPluginGroupDailyAnalysis"><img src="https://fastly.jsdelivr.net/gh/SXP-Simon/astrbot_plugin_qq_group_daily_analysis@main/assets/TG.png" width="300" alt="Telegram Group"></a>
</td>
</tr>
<tr>
<td style="border: none;" align="center"><b>QQ群</b></td>
<td style="border: none;" align="center"><b>Telegram群</b></td>
</tr>
</table>
@@ -365,10 +369,11 @@ src/infrastructure/reporting/templates/your_theme_name/
PDF 调试模板命令:
```bash
# docker
docker exec -it astrbot python data/plugins/astrbot_plugin_qq_group_daily_analysis/scripts/mock_pdf_gen.py
# uv
uv run --with playwright --with diskcache data\plugins\astrbot_plugin_qq_group_daily_analysis\scripts\mock_pdf_gen.py
# 生成所有主题的 PDF (推荐)
uv run --with playwright --with diskcache scripts\debug_all_pdf_themes.py
# 生成指定主题的 PDF (例如:HatsuneMiku ,可以修改内部代码占位解决)
uv run --with playwright --with diskcache scripts\mock_pdf_gen.py
```
Image 模板调试:
@@ -401,6 +406,7 @@ uv run scripts\debug_render.py -t retro_futurism -o debug_retro.html
uv run scripts\debug_render.py -t format -o debug_format.html
uv run scripts\debug_render.py -t simple -o debug_simple.html
uv run scripts\debug_render.py -t spring_festival -o debug_spring.html
uv run scripts\debug_render.py -t HatsuneMiku -o debug_miku.html
```
+1
View File
@@ -69,6 +69,7 @@
"options": [
"scrapbook",
"retro_futurism",
"HatsuneMiku",
"hack",
"spring_festival",
"simple",
+1
View File
@@ -226,6 +226,7 @@ async def main():
"simple",
"spring_festival",
"format",
"HatsuneMiku",
]
for theme in themes:
await generate_theme(theme)
+1 -1
View File
@@ -87,7 +87,7 @@ class MockConfigManager:
return 5
def get_report_template(self):
return "scrapbook"
return "HatsuneMiku"
def get_enable_user_card(self):
return True
@@ -1,6 +1,6 @@
<div class="card-common" style="padding: 30px 20px 15px 20px;">
<div style="display: flex; align-items: flex-end; justify-content: space-between; height: 180px; gap: 4px;">
{# 定义一个从 葱绿 -> 薄荷 -> 鹅黄 -> 蜜桃粉 的完美过渡色板 #}
<div style="display: flex; flex-direction: column; gap: 12px; width: 100%; min-width: 600px;">
{# 柱状图主体层:确保底部对齐 #}
<div style="display: flex; align-items: flex-end; justify-content: space-between; height: 160px; gap: 4px; padding-bottom: 5px; border-bottom: 2px solid rgba(57, 197, 187, 0.1);">
{% set chart_colors = [
'#66D1C7', '#6FD3C6', '#79D5C4', '#85D8C3', '#93DAC1', '#A3DDBF',
'#B4E0BD', '#C7E2BA', '#DBE5B8', '#EFE8B5', '#FFEAB3', '#FFDF9E',
@@ -10,10 +10,7 @@
{% for item in chart_data %}
{% set h = item.percentage | float %}
{# 使用 loop.index0 从色板中依次取色,形成从左到右的全局渐变 #}
{% set bar_color = chart_colors[loop.index0 % 24] %}
{% set bar_opacity = '1' %}
{% if item.count == 0 %}
{% set h = '4' %}
@@ -21,22 +18,27 @@
{% set bar_color = 'var(--miku-blue)' %}
{% endif %}
<div style="flex: 1; display: flex; flex-direction: column; align-items: center; height: 100%; justify-content: flex-end; position: relative;">
<div style="flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; height: 100%;">
{% if item.count > 0 %}
{# 数字去掉背景框,颜色与柱子保持一致,视觉更通透 #}
<span style="font-family: var(--font-num); font-size: 0.8rem; color: {{ bar_color }}; margin-bottom: 6px; font-weight: bold; text-shadow: 0 1px 2px rgba(0,0,0,0.02);">{{ item.count }}</span>
<span style="font-family: var(--font-num); font-size: 0.75rem; color: {{ bar_color }}; margin-bottom: 4px; font-weight: 800;">{{ item.count }}</span>
{% endif %}
{# 柱状体 #}
<div style="width: 100%; height: {{ h }}%;
background: {{ bar_color }};
border-radius: 4px 4px 1px 1px;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
transition: height 0.3s ease;
opacity: {{ bar_opacity }};"></div>
opacity: {{ bar_opacity }};
transition: height 0.3s ease;"></div>
</div>
{% endfor %}
</div>
{# X轴标签 #}
<span style="margin-top: 10px; font-family: var(--font-num); font-size: 0.75rem; color: var(--text-sub); font-weight: bold;">{{ "%02d"|format(item.hour) }}时</span>
{# X轴标签层:独立容器确保水平对齐且不换行 #}
<div style="display: flex; justify-content: space-between; gap: 4px;">
{% for item in chart_data %}
<div style="flex: 1; text-align: center;">
<span style="font-family: var(--font-num); font-size: 0.7rem; color: var(--text-sub); font-weight: bold; white-space: nowrap; display: inline-block;">
{{ "%02d"|format(item.hour) }}{% if loop.index0 % 4 == 0 or loop.last %}时{% endif %}
</span>
</div>
{% endfor %}
</div>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -5,7 +5,8 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>群聊日常分析看板 · {{current_date}}</title>
<link href="https://fonts.googleapis.com/css2?family=ZCOOL+KuaiLe&family=Noto+Sans+SC:wght@400;500;700&family=Share+Tech+Mono&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=ZCOOL+KuaiLe&family=Noto+Sans+SC:wght@400;500;700&family=Outfit:wght@400;600;800&family=Share+Tech+Mono&display=swap" rel="stylesheet">
<script src="https://unpkg.com/lucide@latest"></script>
<style>
:root {
--miku-base: #39C5BB;
@@ -24,7 +25,8 @@
--radius: 12px;
--font-main: 'Noto Sans SC', sans-serif;
--font-title: 'ZCOOL KuaiLe', cursive;
--font-num: 'Share Tech Mono', monospace;
--font-num: 'Outfit', sans-serif;
--font-mono: 'Share Tech Mono', monospace;
--border-miku-light: rgba(57, 197, 187, 0.15);
--border-miku-gradient: linear-gradient(90deg, var(--miku-dark) 0%, var(--miku-base) 100%);
@@ -104,8 +106,9 @@
.stats-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; margin-bottom: 30px; position: relative; z-index: 1; }
.stat-box { background: rgba(255, 255, 255, 0.95); border-radius: var(--radius); padding: 18px; border: 1px solid var(--border-miku-light); box-shadow: 0 4px 12px rgba(57, 197, 187, 0.05); text-align: center; }
.stat-num { font-family: var(--font-num); font-size: 2.2rem; color: var(--text-main); font-weight: bold; }
.stat-num { font-family: var(--font-num); font-size: 2.2rem; color: var(--text-main); font-weight: 800; }
.stat-txt { font-size: 0.85rem; color: var(--text-sub); margin-top: 5px; font-weight: bold;}
.stat-icon { color: var(--miku-base); width: 18px; height: 18px; margin-bottom: 2px; }
.active-peak-block {
grid-column: span 12;
@@ -125,11 +128,11 @@
}
.active-peak-block::after {
content: "MIKU"; position: absolute; right: 15px; top: 0; bottom: 0; display: flex; align-items: center; font-family: var(--font-num); font-size: 160px; line-height: 0.73; font-weight: 900; font-style: italic; color: rgba(255, 255, 255, 0.2); z-index: 0; pointer-events: none; letter-spacing: -3px;
content: "MIKU"; position: absolute; right: 15px; top: 0; bottom: 0; display: flex; align-items: center; font-family: var(--font-mono); font-size: 160px; line-height: 0.73; font-weight: 900; font-style: italic; color: rgba(255, 255, 255, 0.2); z-index: 0; pointer-events: none; letter-spacing: -3px;
}
.peak-label { font-size: 1.1rem; font-weight: bold; text-shadow: 0 1px 2px rgba(0,0,0,0.1); position: relative; z-index: 2;}
.peak-time-text { font-family: var(--font-num); font-size: 2.8rem; font-weight: bold; margin-top: 8px; text-shadow: 0 2px 4px rgba(0,0,0,0.1); letter-spacing: 2px; position: relative; z-index: 2;}
.peak-label { font-size: 1rem; font-weight: bold; text-shadow: 0 1px 2px rgba(0,0,0,0.1); position: relative; z-index: 2; display: flex; align-items: center; gap: 8px;}
.peak-time-text { font-family: var(--font-num); font-size: 2.8rem; font-weight: 800; margin-top: 4px; text-shadow: 0 2px 4px rgba(0,0,0,0.1); letter-spacing: 1px; position: relative; z-index: 2;}
.peak-image-container { height: 100%; width: auto; max-width: 45%; display: flex; align-items: center; justify-content: flex-end; padding: 10px 0; position: relative; z-index: 1; }
.peak-image-container img { height: 100%; width: auto; object-fit: contain; border-radius: 6px; box-shadow: 0 4px 10px rgba(0,0,0,0.15); }
@@ -142,15 +145,14 @@
.footer {
margin-top: 50px;
background: rgba(224, 242, 241, 0.65);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
background: #e0f2f1;
border: 1px solid rgba(57, 197, 187, 0.3);
border-radius: var(--radius);
padding: 20px 30px;
position: relative;
z-index: 1;
box-shadow: 0 8px 20px rgba(57, 197, 187, 0.08);
page-break-inside: avoid;
}
.footer-top { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px dashed rgba(57, 197, 187, 0.3); padding-bottom: 15px; margin-bottom: 15px; }
@@ -223,29 +225,29 @@
<div class="stats-row">
<div class="stat-box">
<div style="display: flex; align-items: center; justify-content: center; gap: 8px; margin-bottom: 8px;">
<div style="width: 4px; height: 14px; background: var(--miku-base); border-radius: 2px;"></div>
<div style="display: flex; align-items: center; justify-content: center; gap: 6px; margin-bottom: 5px;">
<i data-lucide="message-square" class="stat-icon"></i>
<div class="stat-txt" style="margin: 0;">今日消息数</div>
</div>
<div class="stat-num">{{message_count}}</div>
</div>
<div class="stat-box">
<div style="display: flex; align-items: center; justify-content: center; gap: 8px; margin-bottom: 8px;">
<div style="width: 4px; height: 14px; background: var(--miku-base); border-radius: 2px;"></div>
<div style="display: flex; align-items: center; justify-content: center; gap: 6px; margin-bottom: 5px;">
<i data-lucide="users" class="stat-icon"></i>
<div class="stat-txt" style="margin: 0;">活跃成员数</div>
</div>
<div class="stat-num">{{participant_count}}</div>
</div>
<div class="stat-box">
<div style="display: flex; align-items: center; justify-content: center; gap: 8px; margin-bottom: 8px;">
<div style="width: 4px; height: 14px; background: var(--miku-base); border-radius: 2px;"></div>
<div style="display: flex; align-items: center; justify-content: center; gap: 6px; margin-bottom: 5px;">
<i data-lucide="smile" class="stat-icon"></i>
<div class="stat-txt" style="margin: 0;">表情互动</div>
</div>
<div class="stat-num">{{emoji_count}}</div>
</div>
<div class="stat-box">
<div style="display: flex; align-items: center; justify-content: center; gap: 8px; margin-bottom: 8px;">
<div style="width: 4px; height: 14px; background: var(--miku-base); border-radius: 2px;"></div>
<div style="display: flex; align-items: center; justify-content: center; gap: 6px; margin-bottom: 5px;">
<i data-lucide="type" class="stat-icon"></i>
<div class="stat-txt" style="margin: 0;">今日字数累计</div>
</div>
<div class="stat-num">{{total_characters}}</div>
@@ -254,7 +256,10 @@
<div class="active-peak-block">
<div>
<div class="peak-label">峰值活跃时间段</div>
<div class="peak-label">
<i data-lucide="trending-up" style="width: 20px; height: 20px;"></i>
峰值活跃时间段
</div>
<div class="peak-time-text">{{most_active_period}}</div>
</div>
@@ -279,17 +284,37 @@
<div class="footer">
<div class="footer-top">
<div class="footer-repo">
Full repository path / <a href="#">SXP-Simon/astrbot_plugin_qq_group_daily_analysis</a>
<svg viewBox="0 0 24 24" fill="var(--miku-dark)" style="width: 18px; height: 18px; vertical-align: middle; margin-right: 6px;">
<path d="M12 0C5.374 0 0 5.373 0 12c0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.042-1.416-4.042-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.652.242 2.873.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576C20.566 21.797 24 17.3 24 12c0-6.627-5.373-12-12-12z"/>
</svg>
<a href="https://github.com/SXP-Simon/astrbot_plugin_qq_group_daily_analysis" target="_blank">SXP-Simon/astrbot_plugin_qq_group_daily_analysis</a>
</div>
<div class="token-data">
Token usage: {{total_tokens}} <span class="token-sub">( P: {{prompt_tokens}} | C: {{completion_tokens}} )</span>
<svg viewBox="0 0 24 24" stroke="var(--text-sub)" stroke-width="2" fill="none" style="width: 14px; height: 14px; vertical-align: middle; margin-right: 4px;">
<rect x="4" y="4" width="16" height="16" rx="2" ry="2"></rect>
<path d="M9 9h6v6H9z"></path><path d="M9 1v3"></path><path d="M15 1v3"></path><path d="M9 20v3"></path><path d="M15 20v3"></path><path d="M20 9h3"></path><path d="M20 15h3"></path><path d="M1 9h3"></path><path d="M1 15h3"></path>
</svg>
Token usage: <span style="font-weight: 800;">{{total_tokens}}</span> <span class="token-sub">( P: {{prompt_tokens}} | C: {{completion_tokens}} )</span>
</div>
</div>
<div class="footer-bottom">
<div class="footer-time">{{current_datetime}}</div>
<div class="footer-author">Template by shiitin</div>
<div class="footer-time">
<svg viewBox="0 0 24 24" stroke="var(--text-sub)" stroke-width="2" fill="none" style="width: 14px; height: 14px; vertical-align: middle; margin-right: 4px;">
<circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline>
</svg>
Generated at {{current_datetime}}
</div>
<div class="footer-author">
<svg viewBox="0 0 24 24" fill="#ff85a2" style="width: 14px; height: 14px; vertical-align: middle; margin-right: 4px;">
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path>
</svg>
Template by shiitin
</div>
</div>
</div>
</div>
<script>
lucide.createIcons();
</script>
</body>
</html>
@@ -1,9 +1,4 @@
{% if quotes %}
<div style="margin-bottom: 40px;">
<div class="section-header">
<div class="v-bar"></div>
<div class="section-title-text">每日回响</div>
</div>
{% for quote in quotes %}
<div style="margin-bottom: 25px; display: flex; flex-direction: column; align-items: {% if loop.index0 is even %}flex-start{% else %}flex-end{% endif %};">
<div style="display: flex; gap: 15px; max-width: 85%; {% if loop.index0 is odd %}flex-direction: row-reverse;{% endif %}">
@@ -19,8 +14,11 @@
<div style="display: inline-block; background: var(--bg-page-base); padding: 12px 18px; border-radius: 12px; border-left: 4px solid var(--miku-pink-light); text-align: left;">
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 6px;">
<span style="font-size: 1.1rem;">🎵</span>
<span style="font-weight: bold; color: var(--miku-dark); font-size: 0.95rem;">Miku 锐评</span>
<svg viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none" style="width: 18px; height: 18px; color: var(--miku-dark);">
<path d="M9 18V5l12-2v13"/>
<circle cx="6" cy="18" r="3"/>
<circle cx="18" cy="16" r="3"/>
</svg> <span style="font-weight: bold; color: var(--miku-dark); font-size: 0.95rem;">Miku 锐评</span>
</div>
<div style="font-size: 0.9rem; color: var(--text-sub); line-height: 1.6;">{{ quote.reason | safe }}</div>
</div>
@@ -28,5 +26,4 @@
</div>
</div>
{% endfor %}
</div>
{% endif %}
File diff suppressed because one or more lines are too long
@@ -1,29 +1,33 @@
{% if titles %}
<div style="margin-bottom: 40px;">
<div class="section-header">
<div class="v-bar"></div>
<div class="section-title-text">群友画像</div>
</div>
<div style="column-count: 2; column-gap: 15px;">
{% for title in titles %}
<div class="card-common" style="break-inside: avoid; padding: 20px; margin-bottom: 15px; border-top: 3px solid var(--miku-light);">
<div style="display: flex; align-items: center; gap: 15px; margin-bottom: 15px;">
<div style="width: 55px; height: 55px; border-radius: 50%; border: 3px solid white; box-shadow: 0 0 0 2px var(--miku-light); overflow: hidden; flex-shrink: 0; background: var(--bg-page-base);">
{% if title.avatar_data %}<img src="{{ title.avatar_data }}" style="width: 100%; height: 100%; object-fit: cover;">{% endif %}
</div>
<div>
<div style="font-weight: bold; font-size: 1.1rem; color: var(--text-main);">{{ title.name }}</div>
<div style="display: flex; gap: 6px; margin-top: 6px;">
<span style="background: var(--miku-blue); color: var(--miku-dark); font-size: 0.75rem; padding: 3px 8px; border-radius: 6px; font-weight: bold;">{{ title.title }}</span>
<span style="background: var(--miku-pink-light); color: #fff; font-size: 0.75rem; padding: 3px 8px; border-radius: 6px; font-family: var(--font-num); font-weight: bold;">{{ title.mbti }}</span>
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px;">
{% for title in titles %}
<div class="card-common" style="padding: 24px; margin-bottom: 0; border-top: 4px solid var(--miku-light); display: flex; flex-direction: column; gap: 15px;">
<div style="display: flex; align-items: center; gap: 15px;">
<div style="width: 60px; height: 60px; border-radius: 50%; border: 3px solid white; box-shadow: 0 4px 10px rgba(57,197,187,0.2); overflow: hidden; flex-shrink: 0; background: var(--bg-page-base);">
{% if title.avatar_data %}
<img src="{{ title.avatar_data }}" style="width: 100%; height: 100%; object-fit: cover;">
{% else %}
<div style="width: 100%; height: 100%; background: var(--miku-blue); display: flex; align-items: center; justify-content: center; color: white; font-weight: bold;">
{{ title.name[0] if title.name else '?' }}
</div>
</div>
{% endif %}
</div>
<div style="background: var(--bg-page-base); padding: 12px 15px; border-radius: 8px; font-size: 0.95rem; color: var(--text-sub); line-height: 1.6; border: 1px dashed var(--border-miku-light);">
{{ title.reason }}
<div style="flex: 1; overflow: hidden;">
<div style="font-weight: 800; font-size: 1.15rem; color: var(--accent-dark); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">{{ title.name }}</div>
<div style="display: flex; gap: 6px; margin-top: 6px;">
<span style="background: var(--miku-blue); color: var(--miku-dark); font-size: 0.7rem; padding: 2px 8px; border-radius: 6px; font-weight: 800; border: 1px solid rgba(57,197,187,0.3);">{{ title.title }}</span>
{% if title.mbti %}
<span style="background: var(--miku-pink-light); color: white; font-size: 0.7rem; padding: 2px 8px; border-radius: 6px; font-family: var(--font-num); font-weight: 800; box-shadow: 0 2px 5px rgba(255,107,149,0.2);">{{ title.mbti }}</span>
{% endif %}
</div>
</div>
</div>
{% endfor %}
<div style="background: var(--bg-page-base); padding: 15px; border-radius: 12px; font-size: 0.95rem; color: var(--text-sub); line-height: 1.6; border: 1px dashed var(--border-miku-light); position: relative; flex: 1;">
{{ title.reason }}
<svg viewBox="0 0 24 24" fill="var(--miku-base)" style="position: absolute; right: 8px; bottom: 8px; width: 16px; height: 16px; opacity: 0.2;">
<path d="M3 21c3 0 7-1 7-8V5c0-1.25-.75-2-2-2H4c-1.25 0-2 .75-2 2v5c0 1.25.75 2 2 2h3c0 4-4 6-4 6Zm11 0c3 0 7-1 7-8V5c0-1.25-.75-2-2-2h-4c-1.25 0-2 .75-2 2v5c0 1.25.75 2 2 2h3c0 4-4 6-4 6Z"/>
</svg> </div>
</div>
{% endfor %}
</div>
{% endif %}