This commit is contained in:
xiaoxi68
2025-11-09 20:28:51 +08:00
parent f42929938d
commit 4f509ae5f9
29 changed files with 1390 additions and 978 deletions
+1 -4
View File
@@ -6,7 +6,4 @@
from .pdf_utils import PDFInstaller
from .helpers import MessageAnalyzer
__all__ = [
'PDFInstaller',
'MessageAnalyzer'
]
__all__ = ["PDFInstaller", "MessageAnalyzer"]
+49 -16
View File
@@ -4,12 +4,13 @@
"""
from typing import List, Dict
from ...src.models.data_models import GroupStatistics, SummaryTopic, UserTitle, GoldenQuote, TokenUsage
from ...src.models.data_models import TokenUsage
from ...src.core.message_handler import MessageHandler
from ...src.analysis.llm_analyzer import LLMAnalyzer
from ...src.analysis.statistics import UserAnalyzer
from astrbot.api import logger
class MessageAnalyzer:
"""消息分析器 - 整合所有分析功能"""
@@ -28,7 +29,9 @@ class MessageAnalyzer:
else:
await self.message_handler.set_bot_qq_id(bot_instance)
async def analyze_messages(self, messages: List[Dict], group_id: str, unified_msg_origin: str = None) -> Dict:
async def analyze_messages(
self, messages: List[Dict], group_id: str, unified_msg_origin: str = None
) -> Dict:
"""完整的消息分析流程"""
try:
# 基础统计
@@ -36,11 +39,15 @@ class MessageAnalyzer:
# 用户分析
user_analysis = self.user_analyzer.analyze_users(messages)
# 获取活跃用户列表 - 使用get_top_users方法,limit从配置中读取
max_user_titles = self.config_manager.get_max_user_titles()
top_users = self.user_analyzer.get_top_users(user_analysis, limit=max_user_titles)
logger.info(f"获取到 {len(top_users)} 个活跃用户用于称号分析(配置上限: {max_user_titles})")
top_users = self.user_analyzer.get_top_users(
user_analysis, limit=max_user_titles
)
logger.info(
f"获取到 {len(top_users)} 个活跃用户用于称号分析(配置上限: {max_user_titles})"
)
# LLM分析 - 使用并发方式
topics = []
@@ -51,35 +58,61 @@ class MessageAnalyzer:
# 检查各个分析功能是否启用
topic_enabled = self.config_manager.get_topic_analysis_enabled()
user_title_enabled = self.config_manager.get_user_title_analysis_enabled()
golden_quote_enabled = self.config_manager.get_golden_quote_analysis_enabled()
golden_quote_enabled = (
self.config_manager.get_golden_quote_analysis_enabled()
)
# 如果三个分析都启用,使用并发执行
if topic_enabled and user_title_enabled and golden_quote_enabled:
# 并发执行所有三个分析任务,传入活跃用户列表
topics, user_titles, golden_quotes, total_token_usage = await self.llm_analyzer.analyze_all_concurrent(
(
topics,
user_titles,
golden_quotes,
total_token_usage,
) = await self.llm_analyzer.analyze_all_concurrent(
messages, user_analysis, umo=unified_msg_origin, top_users=top_users
)
else:
# 如果只启用部分分析,则按需执行
if topic_enabled:
topics, topic_tokens = await self.llm_analyzer.analyze_topics(messages, umo=unified_msg_origin)
topics, topic_tokens = await self.llm_analyzer.analyze_topics(
messages, umo=unified_msg_origin
)
total_token_usage.prompt_tokens += topic_tokens.prompt_tokens
total_token_usage.completion_tokens += topic_tokens.completion_tokens
total_token_usage.completion_tokens += (
topic_tokens.completion_tokens
)
total_token_usage.total_tokens += topic_tokens.total_tokens
if user_title_enabled:
# 传入活跃用户列表
user_titles, title_tokens = await self.llm_analyzer.analyze_user_titles(
messages, user_analysis, umo=unified_msg_origin, top_users=top_users
(
user_titles,
title_tokens,
) = await self.llm_analyzer.analyze_user_titles(
messages,
user_analysis,
umo=unified_msg_origin,
top_users=top_users,
)
total_token_usage.prompt_tokens += title_tokens.prompt_tokens
total_token_usage.completion_tokens += title_tokens.completion_tokens
total_token_usage.completion_tokens += (
title_tokens.completion_tokens
)
total_token_usage.total_tokens += title_tokens.total_tokens
if golden_quote_enabled:
golden_quotes, quote_tokens = await self.llm_analyzer.analyze_golden_quotes(messages, umo=unified_msg_origin)
(
golden_quotes,
quote_tokens,
) = await self.llm_analyzer.analyze_golden_quotes(
messages, umo=unified_msg_origin
)
total_token_usage.prompt_tokens += quote_tokens.prompt_tokens
total_token_usage.completion_tokens += quote_tokens.completion_tokens
total_token_usage.completion_tokens += (
quote_tokens.completion_tokens
)
total_token_usage.total_tokens += quote_tokens.total_tokens
# 更新统计数据
@@ -90,7 +123,7 @@ class MessageAnalyzer:
"statistics": statistics,
"topics": topics,
"user_titles": user_titles,
"user_analysis": user_analysis
"user_analysis": user_analysis,
}
except Exception as e:
+84 -66
View File
@@ -5,21 +5,22 @@ PDF工具模块
import sys
import asyncio
import threading
from concurrent.futures import ThreadPoolExecutor
from astrbot.api import logger
class PDFInstaller:
"""PDF功能安装器"""
# 类级别的线程池,用于异步下载任务
_executor = ThreadPoolExecutor(max_workers=1, thread_name_prefix="chromium_download")
_executor = ThreadPoolExecutor(
max_workers=1, thread_name_prefix="chromium_download"
)
_download_status = {
"in_progress": False,
"completed": False,
"failed": False,
"error_message": None
"error_message": None,
}
@staticmethod
@@ -31,10 +32,14 @@ class PDFInstaller:
# 使用asyncio安装pyppeteer和兼容的websockets版本
logger.info("安装 pyppeteer==1.0.2 和兼容的依赖...")
process = await asyncio.create_subprocess_exec(
sys.executable, "-m", "pip", "install",
"pyppeteer==1.0.2", "websockets==10.4",
sys.executable,
"-m",
"pip",
"install",
"pyppeteer==1.0.2",
"websockets==10.4",
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await process.communicate()
@@ -63,20 +68,20 @@ class PDFInstaller:
"""通过 pyppeteer 自动安装 Chromium(异步非阻塞方式)"""
try:
logger.info("正在通过 pyppeteer 自动安装 Chromium...")
# 检查是否已经在下载中
if PDFInstaller._download_status["in_progress"]:
return "⏳ Chromium 正在后台下载中,请稍候..."
# 启动异步下载任务
PDFInstaller._download_status["in_progress"] = True
PDFInstaller._download_status["completed"] = False
PDFInstaller._download_status["failed"] = False
PDFInstaller._download_status["error_message"] = None
# 在后台线程中启动下载
asyncio.create_task(PDFInstaller._background_chromium_download())
return """⏳ Chromium 下载已在后台启动
这可能需要几分钟时间,请稍候...
@@ -96,31 +101,35 @@ class PDFInstaller:
"""后台下载 Chromium,带超时控制"""
try:
logger.info("后台 Chromium 下载任务开始")
# 设置10分钟超时
timeout_seconds = 600
try:
# 使用 asyncio.wait_for 实现超时控制
success = await asyncio.wait_for(
PDFInstaller._download_chromium_via_pyppeteer(),
timeout=timeout_seconds
timeout=timeout_seconds,
)
if success:
PDFInstaller._download_status["completed"] = True
PDFInstaller._download_status["failed"] = False
logger.info("✅ Chromium 后台下载完成!")
else:
PDFInstaller._download_status["failed"] = True
PDFInstaller._download_status["error_message"] = "下载失败,请检查网络连接"
PDFInstaller._download_status["error_message"] = (
"下载失败,请检查网络连接"
)
logger.error("❌ Chromium 下载失败")
except asyncio.TimeoutError:
PDFInstaller._download_status["failed"] = True
PDFInstaller._download_status["error_message"] = f"下载超时({timeout_seconds}秒)"
PDFInstaller._download_status["error_message"] = (
f"下载超时({timeout_seconds}秒)"
)
logger.error(f"❌ Chromium 下载超时({timeout_seconds}秒)")
except Exception as e:
PDFInstaller._download_status["failed"] = True
PDFInstaller._download_status["error_message"] = str(e)
@@ -133,96 +142,101 @@ class PDFInstaller:
"""通过 pyppeteer 自动下载 Chromium(带重试机制)"""
max_retries = 2
retry_count = 0
while retry_count <= max_retries:
try:
if retry_count > 0:
logger.info(f"正在重试下载 Chromium(第 {retry_count}/{max_retries} 次)...")
logger.info(
f"正在重试下载 Chromium(第 {retry_count}/{max_retries} 次)..."
)
else:
logger.info("通过 pyppeteer 自动下载 Chromium...")
# 导入 pyppeteer 并尝试下载
try:
import pyppeteer
from pyppeteer import launch
from pyppeteer.errors import BrowserError
# 方法1: 尝试直接下载 Chromium 而不启动浏览器
logger.info("尝试直接下载 Chromium...")
try:
from pyppeteer.launcher import Launcher
# 创建 Launcher 实例但不启动浏览器
launcher = Launcher(
headless=True,
args=['--no-sandbox', '--disable-setuid-sandbox']
args=["--no-sandbox", "--disable-setuid-sandbox"],
)
# 只下载 Chromium
chromium_revision = launcher._get_chromium_revision()
await launcher._download_chromium()
logger.info("✅ Chromium 下载完成")
return True
except Exception as download_error:
logger.warning(f"直接下载 Chromium 失败: {download_error}")
logger.info("尝试通过启动浏览器来下载...")
# 方法2: 通过启动浏览器触发自动下载
import platform
system = platform.system().lower()
if system == "linux":
browser_args = [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-accelerated-2d-canvas',
'--no-first-run',
'--no-zygote',
'--disable-gpu',
'--disable-background-timer-throttling',
'--disable-backgrounding-occluded-windows',
'--disable-renderer-backgrounding',
'--disable-features=TranslateUI',
'--disable-ipc-flooding-protection'
"--no-sandbox",
"--disable-setuid-sandbox",
"--disable-dev-shm-usage",
"--disable-accelerated-2d-canvas",
"--no-first-run",
"--no-zygote",
"--disable-gpu",
"--disable-background-timer-throttling",
"--disable-backgrounding-occluded-windows",
"--disable-renderer-backgrounding",
"--disable-features=TranslateUI",
"--disable-ipc-flooding-protection",
]
else:
browser_args = [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-gpu'
"--no-sandbox",
"--disable-setuid-sandbox",
"--disable-gpu",
]
logger.info("启动 pyppeteer 浏览器以触发 Chromium 自动下载...")
browser = await launch(
headless=True,
args=browser_args,
ignoreHTTPSErrors=True,
dumpio=False # 关闭浏览器日志输出以减少干扰
dumpio=False, # 关闭浏览器日志输出以减少干扰
)
# 获取 Chromium 路径
chromium_path = pyppeteer.executablePath()
logger.info(f"✅ Chromium 自动下载完成,路径: {chromium_path}")
await browser.close()
return True
except BrowserError as e:
logger.error(f"浏览器错误: {e}")
# 方法3: 使用子进程命令行触发下载
try:
logger.info("尝试使用命令行触发 Chromium 自动下载...")
import platform
system = platform.system().lower()
if system == "linux":
cmd = [
sys.executable, "-c",
sys.executable,
"-c",
"""
import pyppeteer
import asyncio
@@ -246,33 +260,34 @@ async def download_chrome():
raise
asyncio.run(download_chrome())
"""
""",
]
else:
cmd = [
sys.executable, "-c",
"import pyppeteer; import asyncio; asyncio.run(pyppeteer.launch())"
sys.executable,
"-c",
"import pyppeteer; import asyncio; asyncio.run(pyppeteer.launch())",
]
process = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await process.communicate()
if process.returncode == 0:
logger.info("✅ 成功通过命令行触发 Chromium 自动下载")
return True
else:
logger.error(f"命令行触发自动下载失败: {stderr.decode()}")
raise Exception(f"命令行下载失败: {stderr.decode()}")
except Exception as e2:
logger.error(f"命令行触发自动下载失败: {e2}")
raise
except Exception as e:
retry_count += 1
if retry_count <= max_retries:
@@ -280,9 +295,12 @@ asyncio.run(download_chrome())
logger.warning(f"下载失败,{wait_time}秒后重试... 错误: {e}")
await asyncio.sleep(wait_time)
else:
logger.error(f"通过 pyppeteer 自动下载 Chromium 失败(已重试{max_retries}次): {e}", exc_info=True)
logger.error(
f"通过 pyppeteer 自动下载 Chromium 失败(已重试{max_retries}次): {e}",
exc_info=True,
)
return False
return False
@staticmethod
@@ -292,4 +310,4 @@ asyncio.run(download_chrome())
version = config_manager.pyppeteer_version or "未知版本"
return f"✅ PDF 功能可用 (pyppeteer {version})"
else:
return "❌ PDF 功能不可用 - 需要安装 pyppeteer"
return "❌ PDF 功能不可用 - 需要安装 pyppeteer"