diff --git a/.gitignore b/.gitignore
index dc51f89..4798225 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,4 @@ data/cmd_config.json
data/t2i_templates/astrbot_powershell.html
data/t2i_templates/base.html
.sisyphus/
+group-insight-main/
\ No newline at end of file
diff --git a/_conf_schema.json b/_conf_schema.json
index 8fa8d3c..6aa4a98 100644
--- a/_conf_schema.json
+++ b/_conf_schema.json
@@ -316,7 +316,7 @@
"type": "text",
"editor_mode": true,
"editor_language": "markdown",
- "default": "你是一个帮我进行群聊信息总结的助手,生成总结内容时,你需要严格遵守下面的几个准则:\n\n请分析接下来提供的群聊记录,提取出最多 **{max_topics}** 个主要话题。\n\n## 对于每个话题,请提供:\n\n1. **话题名称**(突出主题内容,尽量简明扼要)\n2. **主要参与者**(最多5人)\n3. **话题详细描述**(包含关键信息和结论)\n\n## 注意事项:\n\n- 对于比较有价值的点,稍微用一两句话详细讲讲,比如不要生成 \"Nolan 和 SOV 讨论了 galgame 中关于性符号的衍生情况\" 这种宽泛的内容,而是生成更加具体的讨论内容,让其他人只看这个消息就能知道讨论中有价值的、有营养的信息。\n- 对于其中的部分信息,你需要特意提到主题施加的主体是谁,是哪个群友做了什么事情,而不要直接生成和群友没有关系的语句。\n- 对于每一条总结,尽量讲清楚前因后果,以及话题的结论:是什么、为什么、怎么做。如果用户没有讲到细节,则可以不用这么做。\n\n## 群聊记录:\n\n{messages_text}\n\n---\n\n## 重要:必须返回标准 JSON 格式\n\n严格遵守以下规则:\n\n1. 只使用英文双引号 `\"` ,不要使用中文引号 `\"` `\"`\n2. 字符串内容中的引号必须转义为 `\\\"`\n3. 多个对象之间用逗号分隔\n4. 数组元素之间用逗号分隔\n5. 不要在 JSON 外添加任何文字说明\n6. 描述内容避免使用特殊符号,用普通文字表达\n\n### 返回格式示例:\n\n```json\n[\n {{\n \"topic\": \"话题名称\",\n \"contributors\": [\"用户1\", \"用户2\"],\n \"detail\": \"话题描述内容\"\n }},\n {{\n \"topic\": \"另一个话题\",\n \"contributors\": [\"用户3\", \"用户4\"],\n \"detail\": \"另一个话题的描述\"\n }}\n]\n```\n\n**注意**:返回的内容必须是纯 JSON,不要包含 markdown 代码块标记或其他格式。"
+ "default": "你是一个帮我进行群聊信息总结的助手,生成总结内容时,你需要严格遵守下面的几个准则:\n\n请分析接下来提供的群聊记录,提取出最多 **{max_topics}** 个主要话题。\n\n## 对于每个话题,请提供:\n\n1. **话题名称**(突出主题内容,尽量简明扼要)\n2. **主要参与者**(最多5人)\n3. **话题详细描述**(包含关键信息和结论)\n\n## 注意事项:\n\n- 对于比较有价值的点,稍微用一两句话详细讲讲,比如不要生成 \"Nolan 和 SOV 讨论了 galgame 中关于性符号的衍生情况\" 这种宽泛的内容,而是生成更加具体的讨论内容,让其他人只看这个消息就能知道讨论中有价值的、有营养的信息。\n- 对于其中的部分信息,你需要特意提到主题施加的主体是谁,是哪个群友做了什么事情,而不要直接生成和群友没有关系的语句。\n- 对于每一条总结,尽量讲清楚前因后果,以及话题的结论:是什么、为什么、怎么做。如果用户没有讲到细节,则可以不用这么做。\\n- **用户引用**:在话题详情描述中,如果提到了具体用户,请使用 `[用户ID]` 的格式来指代(例如 `[123456]`)。不要只写昵称。我们会自动渲染头像。\\n\\n## 群聊记录:\n\n{messages_text}\n\n---\n\n## 重要:必须返回标准 JSON 格式\n\n严格遵守以下规则:\n\n1. 只使用英文双引号 `\"` ,不要使用中文引号 `\"` `\"`\n2. 字符串内容中的引号必须转义为 `\\\"`\n3. 多个对象之间用逗号分隔\n4. 数组元素之间用逗号分隔\n5. 不要在 JSON 外添加任何文字说明\n6. 描述内容避免使用特殊符号,用普通文字表达\n\n### 返回格式示例:\n\n```json\n[\n {{\n \"topic\": \"话题名称\",\n \"contributors\": [\"用户1\", \"用户2\"],\n \"detail\": \"话题描述内容\"\n }},\n {{\n \"topic\": \"另一个话题\",\n \"contributors\": [\"用户3\", \"用户4\"],\n \"detail\": \"另一个话题的描述\"\n }}\n]\n```\n\n**注意**:返回的内容必须是纯 JSON,不要包含 markdown 代码块标记或其他格式。"
}
}
},
diff --git a/src/application/services/analysis_application_service.py b/src/application/services/analysis_application_service.py
index 46752e2..c077caf 100644
--- a/src/application/services/analysis_application_service.py
+++ b/src/application/services/analysis_application_service.py
@@ -309,7 +309,12 @@ class AnalysisApplicationService:
# 8. 构建 IncrementalBatch
# 8a. 转换话题: SummaryTopic -> dict
new_topics = [
- {"topic": t.topic, "contributors": t.contributors, "detail": t.detail}
+ {
+ "topic": t.topic,
+ "contributors": t.contributors,
+ "detail": t.detail,
+ "contributor_ids": t.contributor_ids,
+ }
for t in topics
]
diff --git a/src/domain/models/data_models.py b/src/domain/models/data_models.py
index 4492767..b9ddef1 100644
--- a/src/domain/models/data_models.py
+++ b/src/domain/models/data_models.py
@@ -13,6 +13,7 @@ class SummaryTopic:
topic: str
contributors: list[str]
detail: str
+ contributor_ids: list[str] = field(default_factory=list) # 贡献者ID列表 (用于显示头像)
@dataclass
diff --git a/src/domain/services/incremental_merge_service.py b/src/domain/services/incremental_merge_service.py
index 92ddee0..a954c40 100644
--- a/src/domain/services/incremental_merge_service.py
+++ b/src/domain/services/incremental_merge_service.py
@@ -237,6 +237,7 @@ class IncrementalMergeService:
topic=topic_dict.get("topic", "未知话题"),
contributors=topic_dict.get("contributors", []),
detail=topic_dict.get("detail", ""),
+ contributor_ids=topic_dict.get("contributor_ids", []),
)
topics.append(topic)
diff --git a/src/infrastructure/analysis/analyzers/topic_analyzer.py b/src/infrastructure/analysis/analyzers/topic_analyzer.py
index 2e16883..ead87b1 100644
--- a/src/infrastructure/analysis/analyzers/topic_analyzer.py
+++ b/src/infrastructure/analysis/analyzers/topic_analyzer.py
@@ -130,7 +130,12 @@ class TopicAnalyzer(BaseAnalyzer):
cleaned_text = re.sub(r"[\x00-\x1f\x7f-\x9f]", "", cleaned_text)
text_messages.append(
- {"sender": nickname, "time": msg_time, "content": cleaned_text}
+ {
+ "sender": nickname,
+ "time": msg_time,
+ "content": cleaned_text,
+ "user_id": str(user_id),
+ }
)
except Exception as e:
logger.error(
@@ -145,7 +150,7 @@ class TopicAnalyzer(BaseAnalyzer):
# 构建消息文本
messages_text = "\n".join(
[
- f"[{msg['time']}] {msg['sender']}: {msg['content']}"
+ f"[{msg['time']}] [{msg['user_id']}] {msg['sender']}: {msg['content']}"
for msg in text_messages
]
)
@@ -289,6 +294,7 @@ class TopicAnalyzer(BaseAnalyzer):
"sender": nickname,
"time": msg_time,
"content": cleaned_text.strip(),
+ "user_id": str(sender.get("user_id", "")),
}
)
return text_messages
@@ -332,8 +338,34 @@ class TopicAnalyzer(BaseAnalyzer):
logger.debug(f"第一条文本消息类型: {type(text_messages[0])}")
logger.debug(f"第一条文本消息内容: {text_messages[0]}")
+ # 建立昵称到ID的映射表
+ nickname_to_id = {}
+ for msg in text_messages:
+ sender = msg.get("sender")
+ user_id = msg.get("user_id")
+ if sender and user_id:
+ nickname_to_id[sender] = user_id
+
# 直接传入原始消息,让 build_prompt 方法处理
- return await self.analyze(messages, umo, session_id)
+ topics, usage = await self.analyze(messages, umo, session_id)
+
+ # 回填贡献者 ID
+ for topic in topics:
+ ids = set()
+ for contributor in topic.contributors:
+ # 尝试精确匹配
+ if contributor in nickname_to_id:
+ ids.add(nickname_to_id[contributor])
+ else:
+ # 尝试模糊匹配(LLM可能会简化名字)
+ for nick, uid in nickname_to_id.items():
+ if contributor in nick or nick in contributor:
+ ids.add(uid)
+ # 找到一个匹配即可,避免过度匹配
+ break
+ topic.contributor_ids = list(ids)
+
+ return topics, usage
except Exception as e:
logger.error(f"话题分析失败: {e}", exc_info=True)
diff --git a/src/infrastructure/reporting/generators.py b/src/infrastructure/reporting/generators.py
index 70f185d..c78da0a 100644
--- a/src/infrastructure/reporting/generators.py
+++ b/src/infrastructure/reporting/generators.py
@@ -237,12 +237,19 @@ class ReportGenerator(IReportGenerator):
# 使用Jinja2模板构建话题HTML(批量渲染)
max_topics = self.config_manager.get_max_topics()
topics_list = []
+ user_analysis = analysis_result.get("user_analysis")
+
for i, topic in enumerate(topics[:max_topics], 1):
+ # 处理话题详情中的用户引用头像
+ processed_detail = await self._process_topic_detail(
+ topic.detail, avatar_getter, user_analysis
+ )
topics_list.append(
{
"index": i,
"topic": topic,
"contributors": "、".join(topic.contributors),
+ "detail": processed_detail,
}
)
@@ -330,6 +337,58 @@ class ReportGenerator(IReportGenerator):
logger.info(f"渲染数据准备完成,包含 {len(render_data)} 个字段")
return render_data
+ async def _process_topic_detail(
+ self, detail: str, avatar_getter, user_analysis: dict = None
+ ) -> str:
+ """
+ 处理话题详情,将 [123456] 格式的用户引用替换为头像+名称的胶囊样式
+ """
+ import re
+
+ pattern = r"\[(\d+)\]"
+ matches = re.findall(pattern, detail)
+ if not matches:
+ return detail
+
+ unique_ids = set(matches)
+ avatars = {}
+
+ # 并发获取头像
+ for uid in unique_ids:
+ avatars[uid] = await self._get_user_avatar(uid, avatar_getter)
+
+ def replacer(match):
+ uid = match.group(1)
+ url = avatars.get(uid)
+
+ name = None
+ if user_analysis and uid in user_analysis:
+ name = user_analysis[uid].get("name")
+
+ if url and name:
+ # 胶囊样式 (Capsule Style)
+ capsule_style = (
+ "display:inline-flex;align-items:center;background:rgba(0,0,0,0.05);"
+ "padding:2px 6px 2px 2px;border-radius:12px;margin:0 2px;"
+ "vertical-align:middle;border:1px solid rgba(0,0,0,0.1);text-decoration:none;"
+ )
+ img_style = "width:18px;height:18px;border-radius:50%;margin-right:4px;display:block;"
+ name_style = "font-size:0.85em;color:inherit;font-weight:500;line-height:1;"
+
+ return (
+ f''
+ f''
+ f'{name}'
+ f''
+ )
+ elif url:
+ # 仅有头像回退
+ return f'
'
+
+ return match.group(0)
+
+ return re.sub(pattern, replacer, detail)
+
def _render_html_template(self, template: str, data: dict) -> str:
"""HTML模板渲染,使用 {{key}} 占位符格式
diff --git a/src/infrastructure/reporting/templates/format/topic_item.html b/src/infrastructure/reporting/templates/format/topic_item.html
index 70f96de..7ee761e 100644
--- a/src/infrastructure/reporting/templates/format/topic_item.html
+++ b/src/infrastructure/reporting/templates/format/topic_item.html
@@ -9,7 +9,7 @@
{{ topic.topic.topic }}
{{ topic.topic.detail }}
+{{ topic.detail }}
{{ item.topic.detail }}
+{{ item.detail }}