From 3662ac3d75f0aac97754b0906efd9d5f9a7f6888 Mon Sep 17 00:00:00 2001 From: leko Date: Tue, 31 Mar 2026 20:06:57 +0800 Subject: [PATCH] feat(html): Add HTML output format support (#143 @lekoOwO) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add HTML output format support - Add HTML settings section to _conf_schema.json with base URL and output directory configuration - Add HTML configuration methods to ConfigManager (get_html_output_dir, get_html_base_url, get_html_filename_format) - Create html_template.html files for all themes (fallback to image_template.html) - Implement generate_html_report method in ReportGenerator to save HTML and JSON files - Update main.py to handle HTML output format and send both file and URL if base_url is configured - Update set_output_format command to include HTML as a valid format option Co-authored-by: lekoOwO <20151124+lekoOwO@users.noreply.github.com> * feat: enhance JSON serialization in HTML report generation * fix: 优化输出目录获取逻辑 --------- Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com> Co-authored-by: lekoOwO <20151124+lekoOwO@users.noreply.github.com> Co-authored-by: SXP-Simon --- _conf_schema.json | 33 +- main.py | 34 +- .../analysis/analyzers/base_analyzer.py | 11 +- src/infrastructure/analysis/llm_analyzer.py | 12 +- src/infrastructure/config/config_manager.py | 28 +- src/infrastructure/reporting/generators.py | 151 ++- .../templates/HatsuneMiku/html_template.html | 637 +++++++++ .../templates/format/html_template.html | 541 ++++++++ .../templates/hack/html_template.html | 621 +++++++++ .../retro_futurism/html_template.html | 843 ++++++++++++ .../templates/scrapbook/html_template.html | 1195 +++++++++++++++++ .../templates/simple/html_template.html | 63 + .../spring_festival/html_template.html | 690 ++++++++++ 13 files changed, 4820 insertions(+), 39 deletions(-) create mode 100644 src/infrastructure/reporting/templates/HatsuneMiku/html_template.html create mode 100644 src/infrastructure/reporting/templates/format/html_template.html create mode 100644 src/infrastructure/reporting/templates/hack/html_template.html create mode 100644 src/infrastructure/reporting/templates/retro_futurism/html_template.html create mode 100644 src/infrastructure/reporting/templates/scrapbook/html_template.html create mode 100644 src/infrastructure/reporting/templates/simple/html_template.html create mode 100644 src/infrastructure/reporting/templates/spring_festival/html_template.html diff --git a/_conf_schema.json b/_conf_schema.json index 3201af2..12913c4 100644 --- a/_conf_schema.json +++ b/_conf_schema.json @@ -61,7 +61,7 @@ "type": "string", "description": "输出格式", "default": "image", - "hint": "分析报告的输出格式:image(图片)、text(文本)、pdf(PDF文件)。使用 PDF 需要额外配置,根据文件中的 PDF_功能说明.md 进行配置" + "hint": "分析报告的输出格式:image(图片)、text(文本)、pdf(PDF文件)、html(HTML文件)。使用 PDF 需要额外配置,根据文件中的 PDF_功能说明.md 进行配置" }, "report_template": { "type": "string", @@ -82,7 +82,7 @@ "type": "bool", "description": "调试模式(建议关闭)", "default": false, - "hint": "启用后,会在 plugin_data/debug_data 目录下保存每次分析的平台 API 原始历史消息和 Prompt 原文,用于调试和优化提示词。" + "hint": "启用后,会在插件数据目录下的 debug_data 目录中保存每次分析的平台 API 原始历史消息和 Prompt 原文,用于调试和优化提示词。" }, "enable_base64_image": { "type": "bool", @@ -344,8 +344,8 @@ "pdf_output_dir": { "type": "string", "description": "PDF输出目录", - "default": "data/plugins/astrbot_plugin_qq_group_daily_analysis/reports", - "hint": "PDF报告文件的保存目录" + "default": "", + "hint": "PDF报告文件的保存目录。留空则自动使用插件数据目录下的 reports 目录。" }, "browser_path": { "type": "string", @@ -361,6 +361,31 @@ } } }, + "html": { + "description": "HTML 设置", + "type": "object", + "hint": "HTML 报告输出相关配置,包括外链 Base URL 和报告储存目录", + "items": { + "html_base_url": { + "type": "string", + "description": "外鏈 Base URL", + "default": "", + "hint": "用于生成外链的 Base URL(可留空)。如果设置了此项,发送报告时会在消息中提供超连結 (${BASE_URL}/${FILENAME})。留空则不提供外链。" + }, + "html_output_dir": { + "type": "string", + "description": "报告储存目录", + "default": "", + "hint": "HTML报告文件的保存目录,用于存储生成的 HTML 文件和原始 JSON 数据。留空则自动使用插件数据目录下的 self_hosted_html_reports 目录。" + }, + "html_filename_format": { + "type": "string", + "description": "HTML文件名格式", + "default": "群聊分析报告_{group_id}_{date}.html", + "hint": "HTML文件名格式,支持变量:{group_id}(群号)、{date}(日期)" + } + } + }, "qq_group_upload": { "description": "群文件/群相册上传设置", "type": "object", diff --git a/main.py b/main.py index d36769b..f44745d 100644 --- a/main.py +++ b/main.py @@ -619,7 +619,7 @@ class GroupDailyAnalysis(Star): pdf_path = await self.report_generator.generate_pdf_report( analysis_result, group_id, - avatar_url_getter=avatar_url_getter, + avatar_getter=avatar_url_getter, nickname_getter=nickname_getter, ) if pdf_path: @@ -630,6 +630,31 @@ class GroupDailyAnalysis(Star): else: yield event.plain_result("⚠️ PDF 生成失败。") + elif output_format == "html": + html_path, json_path = await self.report_generator.generate_html_report( + analysis_result, + group_id, + avatar_url_getter=avatar_url_getter, + nickname_getter=nickname_getter, + ) + if html_path: + # 发送 HTML 文件 + if not await adapter.send_file(group_id, html_path): + yield event.chain_result( + [File(name=Path(html_path).name, file=html_path)] + ) + + # 如果配置了外链 Base URL,则也发送超链接 + base_url = self.config_manager.get_html_base_url() + if base_url: + filename = Path(html_path).name + url = f"{base_url.rstrip('/')}/{filename}" + link_message = f"报告已生成: {url}" + if not await adapter.send_text(group_id, link_message): + yield event.plain_result(link_message) + else: + yield event.plain_result("⚠️ HTML 生成失败。") + else: text_report = self.report_generator.generate_text_report(analysis_result) if not await adapter.send_text(group_id, text_report): @@ -640,7 +665,7 @@ class GroupDailyAnalysis(Star): async def set_output_format(self, event: AstrMessageEvent, format_type: str = ""): """ 设置分析报告输出格式(跨平台支持) - 用法: /设置格式 [image|text|pdf] + 用法: /设置格式 [image|text|pdf|html] """ group_id = self._get_group_id_from_event(event) @@ -661,13 +686,14 @@ class GroupDailyAnalysis(Star): • image - 图片格式 (默认) • text - 文本格式 • pdf - PDF 格式 {pdf_status} +• html - HTML 格式 用法: /设置格式 [格式名称]""") return format_type = format_type.lower() - if format_type not in ["image", "text", "pdf"]: - yield event.plain_result("❌ 无效的格式类型,支持: image, text, pdf") + if format_type not in ["image", "text", "pdf", "html"]: + yield event.plain_result("❌ 无效的格式类型,支持: image, text, pdf, html") return if format_type == "pdf" and not self.config_manager.playwright_available: diff --git a/src/infrastructure/analysis/analyzers/base_analyzer.py b/src/infrastructure/analysis/analyzers/base_analyzer.py index 388e460..074ad21 100644 --- a/src/infrastructure/analysis/analyzers/base_analyzer.py +++ b/src/infrastructure/analysis/analyzers/base_analyzer.py @@ -279,16 +279,9 @@ class BaseAnalyzer(ABC, Generic[TDataObject]): session_id: 会话ID """ try: - from pathlib import Path + from astrbot.api.star import StarTools - from astrbot.core.utils.astrbot_path import get_astrbot_plugin_data_path - - plugin_name = "astrbot_plugin_qq_group_daily_analysis" - base_data_path = get_astrbot_plugin_data_path() - if isinstance(base_data_path, str): - base_data_path = Path(base_data_path) - - data_path = base_data_path / plugin_name / "debug_data" + data_path = StarTools.get_data_dir() / "debug_data" data_path.mkdir(parents=True, exist_ok=True) file_name = f"{session_id}_{self.get_data_type()}.txt" diff --git a/src/infrastructure/analysis/llm_analyzer.py b/src/infrastructure/analysis/llm_analyzer.py index 1e1f483..cbc73c5 100644 --- a/src/infrastructure/analysis/llm_analyzer.py +++ b/src/infrastructure/analysis/llm_analyzer.py @@ -461,18 +461,10 @@ class LLMAnalyzer(IAnalysisProvider): """ try: import json - from pathlib import Path - from astrbot.core.utils.astrbot_path import ( - get_astrbot_plugin_data_path, - ) + from astrbot.api.star import StarTools - plugin_name = "astrbot_plugin_qq_group_daily_analysis" - base_data_path = get_astrbot_plugin_data_path() - if isinstance(base_data_path, str): - base_data_path = Path(base_data_path) - - debug_dir = base_data_path / plugin_name / "debug_data" + debug_dir = StarTools.get_data_dir() / "debug_data" debug_dir.mkdir(parents=True, exist_ok=True) msg_file_path = debug_dir / f"{session_id}_messages.json" diff --git a/src/infrastructure/config/config_manager.py b/src/infrastructure/config/config_manager.py index f2c21e0..b3bff7f 100644 --- a/src/infrastructure/config/config_manager.py +++ b/src/infrastructure/config/config_manager.py @@ -4,10 +4,9 @@ """ import sys -from pathlib import Path from astrbot.api import AstrBotConfig -from astrbot.core.utils.astrbot_path import get_astrbot_data_path +from astrbot.api.star import StarTools from ...utils.logger import logger @@ -226,9 +225,7 @@ class ConfigManager: def get_pdf_output_dir(self) -> str: """获取PDF输出目录""" try: - plugin_name = "astrbot_plugin_qq_group_daily_analysis" - data_path = Path(get_astrbot_data_path()) - default_path = data_path / "plugin_data" / plugin_name / "reports" + default_path = StarTools.get_data_dir() / "reports" return self._get_group("pdf").get("pdf_output_dir", str(default_path)) except Exception: return self._get_group("pdf").get( @@ -250,6 +247,27 @@ class ConfigManager: "pdf_filename_format", "群聊分析报告_{group_id}_{date}.pdf" ) + def get_html_output_dir(self) -> str: + """获取HTML输出目录""" + try: + default_path = StarTools.get_data_dir() / "self_hosted_html_reports" + return self._get_group("html").get("html_output_dir", str(default_path)) + except Exception: + return self._get_group("html").get( + "html_output_dir", + "data/plugins/astrbot_plugin_qq_group_daily_analysis/self_hosted_html_reports", + ) + + def get_html_base_url(self) -> str: + """获取HTML外链Base URL""" + return self._get_group("html").get("html_base_url", "") + + def get_html_filename_format(self) -> str: + """获取HTML文件名格式""" + return self._get_group("html").get( + "html_filename_format", "群聊分析报告_{group_id}_{date}.html" + ) + def get_topic_analysis_prompt(self, style: str = "topic_prompt") -> str: """获取话题分析提示词模板""" prompts_config = self._get_group("prompts").get("topic_analysis_prompts", {}) diff --git a/src/infrastructure/reporting/generators.py b/src/infrastructure/reporting/generators.py index 4bf0695..cb44fbb 100644 --- a/src/infrastructure/reporting/generators.py +++ b/src/infrastructure/reporting/generators.py @@ -7,7 +7,9 @@ import asyncio import base64 import os import re -from datetime import datetime +from dataclasses import asdict, is_dataclass +from datetime import date, datetime +from enum import Enum from pathlib import Path import aiohttp @@ -28,6 +30,7 @@ class ReportGenerator(IReportGenerator): def __init__(self, config_manager, data_dir): self._avatar_session = None self.config_manager = config_manager + self.data_dir = data_dir self.activity_visualizer = ActivityVisualizer() self.html_templates = HTMLTemplates(config_manager) # 实例化HTML模板管理器 # 全局 T2I 渲染信号量,保护本地资源 @@ -36,7 +39,9 @@ class ReportGenerator(IReportGenerator): self._render_semaphore = asyncio.Semaphore(max_concurrent) # 运行时缓存,用于在一次分析任务中避免重复下载同一个头像 - self._avatar_cache = Cache(str(data_dir / "avatar")) # user_id -> base64_uri + self._avatar_cache = Cache( + str(self.data_dir / "avatar") + ) # user_id -> base64_uri self._avatar_session_concurrent_semaphore = asyncio.Semaphore( MAX_CONCURRENT_DOWNLOADS ) @@ -205,13 +210,19 @@ class ReportGenerator(IReportGenerator): self, analysis_result: dict, group_id: str, - avatar_url_getter=None, + avatar_getter=None, nickname_getter=None, ) -> str | None: """生成PDF格式的分析报告""" try: - # 确保输出目录存在(使用 asyncio.to_thread 避免阻塞) - output_dir = Path(self.config_manager.get_pdf_output_dir()) + # 获取输出目录。如果未配置,则由 data_dir 推理得出。 + output_dir = self.config_manager.get_pdf_output_dir() + if not output_dir: + output_dir = self.data_dir / "reports" + else: + output_dir = Path(output_dir) + + # 确保输出目录存在 (使用 asyncio.to_thread 避免阻塞) await asyncio.to_thread(output_dir.mkdir, parents=True, exist_ok=True) # 生成文件名 @@ -225,7 +236,7 @@ class ReportGenerator(IReportGenerator): render_data = await self._prepare_render_data( analysis_result, chart_template="activity_chart_pdf.html", - avatar_url_getter=avatar_url_getter, + avatar_url_getter=avatar_getter, nickname_getter=nickname_getter, ) logger.info(f"PDF 渲染数据准备完成,包含 {len(render_data)} 个字段") @@ -254,6 +265,129 @@ class ReportGenerator(IReportGenerator): logger.error(f"生成 PDF 报告失败: {e}") return None + async def generate_html_report( + self, + analysis_result: dict, + group_id: str, + avatar_url_getter=None, + nickname_getter=None, + ) -> tuple[str | None, str | None]: + """ + 生成HTML格式的分析报告,保存到指定目录 + + Args: + analysis_result: 分析结果字典 + group_id: 群组ID + avatar_url_getter: 异步回调函数,接收 user_id 返回 avatar_url/data + nickname_getter: 昵称获取函数 + + Returns: + tuple[str | None, str | None]: (html_path, json_path) - HTML文件路径和JSON文件路径 + """ + try: + import json + + # 获取输出目录。如果未配置,则由 data_dir 推理得出。 + output_dir = self.config_manager.get_html_output_dir() + if not output_dir: + output_dir = self.data_dir / "self_hosted_html_reports" + else: + output_dir = Path(output_dir) + + # 确保输出目录存在 (使用 asyncio.to_thread 避免阻塞) + await asyncio.to_thread(output_dir.mkdir, parents=True, exist_ok=True) + + # 生成文件名 + current_date = datetime.now().strftime("%Y%m%d") + current_time = datetime.now().strftime("%H%M%S") + html_filename = self.config_manager.get_html_filename_format().format( + group_id=group_id, date=current_date + ) + # 为避免同一天多次分析覆盖,添加时间戳 + html_filename_base = html_filename.rsplit(".", 1)[0] + html_filename = f"{html_filename_base}_{current_time}.html" + json_filename = f"{html_filename_base}_{current_time}.json" + + html_path = output_dir / html_filename + json_path = output_dir / json_filename + + # 准备渲染数据 + render_data = await self._prepare_render_data( + analysis_result, + chart_template="activity_chart.html", + avatar_url_getter=avatar_url_getter, + nickname_getter=nickname_getter, + ) + logger.info(f"HTML 渲染数据准备完成,包含 {len(render_data)} 个字段") + + # 生成 HTML 内容(使用 Jinja2 渲染器,尝试 html_template.html,失败则回退到 image_template.html) + html_content = None + try: + html_content = self.html_templates.render_template( + "html_template.html", **render_data + ) + logger.info("使用 html_template.html 渲染成功") + except Exception as e: + logger.warning( + f"html_template.html 不存在或渲染失败,回退到 image_template.html: {e}" + ) + html_content = self.html_templates.render_template( + "image_template.html", **render_data + ) + logger.info("使用 image_template.html 渲染成功") + + # 检查HTML内容是否有效 + if not html_content: + logger.error("HTML报告渲染失败:返回空内容") + return None, None + + logger.info(f"HTML 内容生成完成,长度: {len(html_content)} 字符") + + # 保存 HTML 文件 + await asyncio.to_thread( + html_path.write_text, html_content, encoding="utf-8" + ) + logger.info(f"HTML 报告已保存: {html_path}") + + def json_default_encoder(obj): + if hasattr(obj, "to_dict") and callable(obj.to_dict): + return obj.to_dict() + if is_dataclass(obj) and not isinstance(obj, type): + return asdict(obj) + if isinstance(obj, (datetime, date)): + return obj.isoformat() + if isinstance(obj, Enum): + return obj.value + if isinstance(obj, (set, tuple)): + return list(obj) + raise TypeError( + f"Object of type {type(obj).__name__} is not JSON serializable" + ) + + # 保存原始 JSON 数据 + json_data = { + "analysis_result": analysis_result, + "group_id": group_id, + "generated_at": datetime.now().isoformat(), + } + await asyncio.to_thread( + json_path.write_text, + json.dumps( + json_data, + ensure_ascii=False, + indent=2, + default=json_default_encoder, + ), + encoding="utf-8", + ) + logger.info(f"JSON 数据已保存: {json_path}") + + return str(html_path.absolute()), str(json_path.absolute()) + + except Exception as e: + logger.error(f"生成 HTML 报告失败: {e}", exc_info=True) + return None, None + def generate_text_report(self, analysis_result: dict) -> str: """生成文本格式的分析报告""" stats = analysis_result["statistics"] @@ -555,7 +689,10 @@ class ReportGenerator(IReportGenerator): """ # 1. 检查缓存 (仅包含成功的头像数据) if avatar_id in self._avatar_cache: - return self._avatar_cache[avatar_id] + data = self._avatar_cache[avatar_id] + if isinstance(data, str): + return data + return str(data) # 2. 尝试获取头像字节流 avatar_bytes = await self._get_user_avatar_bytes(avatar_id, avatar_url_getter) diff --git a/src/infrastructure/reporting/templates/HatsuneMiku/html_template.html b/src/infrastructure/reporting/templates/HatsuneMiku/html_template.html new file mode 100644 index 0000000..7375230 --- /dev/null +++ b/src/infrastructure/reporting/templates/HatsuneMiku/html_template.html @@ -0,0 +1,637 @@ + + + + + + + 群聊日常分析看板 · {{current_date}} + + + + + + +
+
01
+
39
+
[ SYSTEM_VOCALOID_INITIALIZED ]
HATSUNE MIKU v4X
+
+ +
+

群聊日常分析看板

+ +
+ +
+
+
+ +
今日消息数
+
+
{{message_count}}
+
+
+
+ +
活跃成员数
+
+
{{participant_count}}
+
+
+
+ +
表情互动
+
+
{{emoji_count}}
+
+
+
+ +
今日字数累计
+
+
{{total_characters}}
+
+
+ +
+
+
+ + 峰值活跃时间段 +
+
{{most_active_period}}
+
+ +
+ Peak Activity Visualization +
+
+ + + {% if hourly_chart_html %} +
+
+
+
+ + 24小时活动 +
+
+
+ {{hourly_chart_html | safe}} +
+
+ {% endif %} + + + {% if topics_html %} +
+
+
+
+ + 话题总结 +
+
+ {{topics_html | safe}} +
+ {% endif %} + + + {% if titles_html %} +
+
+
+
+ + 群友画像 +
+
+ {{titles_html | safe}} +
+ {% endif %} + + + {% if quotes_html %} +
+
+
+
+ + 今日圣经 +
+
+ {{quotes_html | safe}} +
+ {% endif %} + + + {% if chat_quality_html %} +
+
+
+
+ + 群聊锐评 +
+
+ {{chat_quality_html | safe}} +
+ {% endif %} + + +
+ + + + \ No newline at end of file diff --git a/src/infrastructure/reporting/templates/format/html_template.html b/src/infrastructure/reporting/templates/format/html_template.html new file mode 100644 index 0000000..c65073d --- /dev/null +++ b/src/infrastructure/reporting/templates/format/html_template.html @@ -0,0 +1,541 @@ + + + + + + + 群聊日常分析报告 + + + + + +
+
+

群聊日常分析报告

+
{{current_date}}
+
+
+
+

基础统计

+
+
+
{{message_count}}
+
消息总数
+
+
+
{{participant_count}}
+
参与人数
+
+
+
{{total_characters}}
+
总字符数
+
+
+
{{emoji_count}}
+
表情数量
+
+
+
+
{{most_active_period}}
+
最活跃时段
+
+
+ +
+
+
24小时活跃度分布
+
+ {{hourly_chart_html | safe}} +
+ + {% if chat_quality_html %} +
+

群聊质量分析

+ {{ chat_quality_html | safe }} +
+ {% endif %} + + {{topics_html | safe}} + {{titles_html | safe}} + {{quotes_html | safe}} +
+ +
+ + + \ No newline at end of file diff --git a/src/infrastructure/reporting/templates/hack/html_template.html b/src/infrastructure/reporting/templates/hack/html_template.html new file mode 100644 index 0000000..3bec608 --- /dev/null +++ b/src/infrastructure/reporting/templates/hack/html_template.html @@ -0,0 +1,621 @@ + + + + + + Night Mode Analysis Portal + + + + + + + +
+ +
+ + +
+ + + + +
+
+ +
+ +
+
+
// main.ts
+

> Group Activity
Insights

+
// Report for {{current_date}}
+ +
+ const stats = {
+   messages: {{message_count}},
+   active_users: {{participant_count}},
+   peak_time: '{{most_active_period}}'
+ };
+ // Total characters: {{total_characters}} +
+
+ + {% if chat_quality_html %} +
+
+ $ analyze --quality +
+ {{ chat_quality_html | safe }} +
+ {% endif %} + + + {% if topics_html %} +
+
+ $ ls ./topics +
+
+ {{topics_html | safe}} +
+
+ {% endif %} +
+ + +
+
+
+
+
+
+
+
+
trend-analytics.tsx
+
+
+ {{hourly_chart_html | safe}} +
+
+ + + {% if titles_html %} +
+
+ $ cat ./user_titles +
+
+ {{titles_html | safe}} +
+
+ {% endif %} + + + {% if quotes_html %} +
+
+ $ grep -r "golden_quotes" +
+
+ {{quotes_html | safe}} +
+
+ {% endif %} +
+
+ +
+
SYSTEM_REPORT // {{current_datetime}}
+
+ TOKENS: {{total_tokens}} + [P: {{prompt_tokens}} | C: {{completion_tokens}}] +
+
SXP-Simon/astrbot_plugin_qq_group_daily_analysis 群分析
+
+
+ + + \ No newline at end of file diff --git a/src/infrastructure/reporting/templates/retro_futurism/html_template.html b/src/infrastructure/reporting/templates/retro_futurism/html_template.html new file mode 100644 index 0000000..109a52d --- /dev/null +++ b/src/infrastructure/reporting/templates/retro_futurism/html_template.html @@ -0,0 +1,843 @@ + + + + + + Retro Futurism Daily Export + + + + + + + +
+ +
+
+
+
+
+
+
+
System Report // {{current_date}}
+

Daily
Analysis
Export

+
+
+ + + +
+
+ + + +
+
+ + + +
+
+

+ Daily activity analysis report, generated based on message statistics and AI analysis +

+
+
+
Generated At
+ {{current_datetime}} +
+
+
Peak Window
+ {{most_active_period}} +
+
+
+ + +
+
+ Messages +
{{message_count}}
+
累计消息数量
+
+ +
+ Participants +
{{participant_count}}
+
参与者计数
+
+ +
+ Characters +
{{total_characters}}
+
文本总字数
+
+ +
+ Emoji +
{{emoji_count}}
+
表情符号统计
+
+
+ + +
+
+
+ + + +
+
Peak Activity Window
+
{{most_active_period}}
+
活跃序列 SCAN // 消息流强度 {{message_count}}
+
+
+ + + +
+
+
+ {{hourly_chart_html|safe}} +
+
+
+ + + {% if chat_quality_html %} + +
+
+ {{ chat_quality_html|safe }} +
+
+ {% endif %} + + + {% if topics_html %} + +
+ {{topics_html|safe}} +
+ {% endif %} + + + {% if titles_html %} + +
+
+
+ {{titles_html|safe}} +
+
+
+ {% endif %} + + + {% if quotes_html %} + +
+ {{quotes_html|safe}} +
+ {% endif %} + +
+ + +
{{current_datetime}} // END_OF_LINE_
+
+
+ + + \ No newline at end of file diff --git a/src/infrastructure/reporting/templates/scrapbook/html_template.html b/src/infrastructure/reporting/templates/scrapbook/html_template.html new file mode 100644 index 0000000..e684c1e --- /dev/null +++ b/src/infrastructure/reporting/templates/scrapbook/html_template.html @@ -0,0 +1,1195 @@ + + + + + + + 五彩斑斓的一天,来看看群里发生了什么吧! · {{current_date}} + + + + + + + + + +
+ + +
+
+
+

五彩斑斓的一天,来看看群里发生了什么吧!

+
{{current_date}}
+
+
+ + +
+ +
+ +
+
+ + + +
{{message_count}}
+
消息总数
+
+
+ +
+
+ + + +
{{participant_count}}
+
参与人数
+
+
+ +
+
+ + + +
{{emoji_count}}
+
表情统计
+
+
+ +
+
+ + + +
{{total_characters}}
+
总字符数
+
+
+
+ + +
+
+
+ ✨ Highlight Time +
+
{{most_active_period}}
+
(此刻,世界色彩斑斓)
+
+
+ +
+ + +
+
+
+ + + + 24H 活跃轨迹 +
+
+ {{hourly_chart_html | safe}} +
+
+ + {{topics_html | safe}} + {{titles_html | safe}} + {{quotes_html | safe}} + + + {% if chat_quality_html %} +
+
+
+
+ + + + 群聊质量锐评 +
+ {{ chat_quality_html | safe }} +
+ {% endif %} + +
+ + + + +
+ + + \ No newline at end of file diff --git a/src/infrastructure/reporting/templates/simple/html_template.html b/src/infrastructure/reporting/templates/simple/html_template.html new file mode 100644 index 0000000..3453944 --- /dev/null +++ b/src/infrastructure/reporting/templates/simple/html_template.html @@ -0,0 +1,63 @@ + + + + + + Simple Report + + + + +

群聊日报 - {{current_date}}

+ +
+

基础统计

+

消息数: {{message_count}}

+

参与人数: {{participant_count}}

+

总字符数: {{total_characters}}

+

表情数: {{emoji_count}}

+

最活跃时段: {{most_active_period}}

+
+ +
+

活跃度图表

+ {{hourly_chart_html | safe}} +
+ + {% if chat_quality_html %} +
+

群聊质量分析

+ {{ chat_quality_html | safe }} +
+ {% endif %} + + {{topics_html | safe}} + + {{titles_html | safe}} + + {{quotes_html | safe}} + + + + + \ No newline at end of file diff --git a/src/infrastructure/reporting/templates/spring_festival/html_template.html b/src/infrastructure/reporting/templates/spring_festival/html_template.html new file mode 100644 index 0000000..365d0e5 --- /dev/null +++ b/src/infrastructure/reporting/templates/spring_festival/html_template.html @@ -0,0 +1,690 @@ + + + + + + 春节特供 · 群聊日报 + + + + + + + + +
+
+
+
+
+ +
+
+

🧧 春节特供日报 🧧

+
+
—— {{current_date}} · 岁在丙午 ——
+
+ +
+
+
+
消息总数
+
{{message_count}}
+
+
+
参与群友
+
{{participant_count}}
+
+
+
表情统计
+
{{emoji_count}}
+
+
+
总字符数
+
{{total_characters}}
+
+
+ +
+
+ 巅峰时刻 +
+
+ {{most_active_period}} +
+
🏮 鸿运齐天 + · 众友云集 🏮
+
+
+ +
+ + {% if chat_quality_html %} +
+
🏺 聊天质量锐评
+ {{ chat_quality_html | safe }} +
+ {% endif %} + + + {% if topics_html %} +
+
🏮 核心话题回顾
+
+ {{topics_html | safe}} +
+
+ {% endif %} + + +
+
📈 活跃时序图
+
+
ACTIVITY CHRONICLE
+ {{hourly_chart_html | safe}} +
+
+ + + {% if titles_html %} +
+
🎖️ 群友风云榜
+ {{titles_html | safe}} +
+ {% endif %} + + + {% if quotes_html %} +
+
每日金句回响
+
+ {{quotes_html | safe}} +
+
+ {% endif %} +
+ + +
+ + + \ No newline at end of file