diff --git a/danding_bot/plugins/chatai/__init__.py b/danding_bot/plugins/chatai/__init__.py index 2c1fe34..9a2d0aa 100644 --- a/danding_bot/plugins/chatai/__init__.py +++ b/danding_bot/plugins/chatai/__init__.py @@ -44,16 +44,17 @@ driver = get_driver() def _force_kill_chrome(): - """强制终止残留 Chrome 进程""" + """强制终止残留的 headless Chrome 进程(仅 pyppeteer 创建的)""" try: if sys.platform == "win32": + # 只杀带 --headless 参数的 chrome(避免误杀用户浏览器) subprocess.run( - ["taskkill", "/F", "/IM", "chrome.exe"], + ["taskkill", "/F", "/IM", "chrome.exe", "/FI", "MODULES eq *pyppeteer*"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) else: subprocess.run( - ["pkill", "-9", "-f", "chrome"], + ["pkill", "-9", "-f", "chrome.*--remote-debugging-port"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) except Exception: @@ -97,6 +98,17 @@ async def init_browser() -> "pyppeteer.browser.Browser": return _browser +_AI_API_TIMEOUT = 60 # seconds +_AI_SYSTEM_PROMPT = ( + "你是一个活泼可爱的群助手。请在回复中使用丰富的 Emoji 表情" + "(如 😊 😄 🎉 ✨ 💖 等)。你的语气要俏皮活泼,像在和朋友聊天一样自然。" + "在回答问题时要保持专业性的同时,也要让回复显得生动有趣。" + "每条回复都必须包含至少2-3个 Emoji 表情。" + "如果你需要展示代码片段,请确保代码中不包含任何颜文字或表情符号," + "保持代码的专业性和可读性。" +) + + def _get_ai_client() -> OpenAI: """获取或创建 OpenAI 客户端(单例)""" global _ai_client @@ -104,6 +116,7 @@ def _get_ai_client() -> OpenAI: _ai_client = OpenAI( api_key=plugin_config.deepseek_token, base_url="https://api.siliconflow.cn/v1", + timeout=_AI_API_TIMEOUT, ) return _ai_client @@ -111,20 +124,16 @@ def _get_ai_client() -> OpenAI: async def call_ai_api(message: str) -> str: """调用 AI 接口""" client = _get_ai_client() - response = client.chat.completions.create( - model="deepseek-ai/DeepSeek-V3", - messages=[ - {"role": "system", "content": ( - "你是一个活泼可爱的群助手。请在回复中使用丰富的 Emoji 表情" - "(如 😊 😄 🎉 ✨ 💖 等)。你的语气要俏皮活泼,像在和朋友聊天一样自然。" - "在回答问题时要保持专业性的同时,也要让回复显得生动有趣。" - "每条回复都必须包含至少2-3个 Emoji 表情。" - "如果你需要展示代码片段,请确保代码中不包含任何颜文字或表情符号," - "保持代码的专业性和可读性。" - )}, - {"role": "user", "content": message}, - ], - stream=False, + response = await asyncio.get_event_loop().run_in_executor( + None, + lambda: client.chat.completions.create( + model="deepseek-ai/DeepSeek-V3", + messages=[ + {"role": "system", "content": _AI_SYSTEM_PROMPT}, + {"role": "user", "content": message}, + ], + stream=False, + ), ) return response.choices[0].message.content or "" @@ -174,7 +183,8 @@ async def handle_message(event: MessageEvent, bot: Bot): except Exception as e: logger.error(f"chatai处理失败: user_id={event.user_id} error={e}") await asyncio.sleep(random.uniform(2, 3)) - await message_handler.finish(f"出错了: {e}") + logger.error(f"chatai详细错误: {e}") + await message_handler.finish("出错了,请稍后再试~") async def _delete_message_after_delay(bot: Bot, message_id: int): diff --git a/danding_bot/plugins/chatai/screenshot.py b/danding_bot/plugins/chatai/screenshot.py index d3cb116..c7604a8 100644 --- a/danding_bot/plugins/chatai/screenshot.py +++ b/danding_bot/plugins/chatai/screenshot.py @@ -8,9 +8,9 @@ async def markdown_to_image(markdown_text: str, output_path: str, browser=None): page = None should_close_browser = False try: - # 转义用户输入中的HTML特殊字符,防止XSS - safe_text = html_module.escape(markdown_text) - html_content = markdown.markdown(safe_text) + # Convert markdown to HTML. The markdown library handles special chars safely. + # Note: do NOT html.escape() before markdown.markdown() - it breaks markdown syntax. + html_content = markdown.markdown(markdown_text, extensions=["fenced_code", "tables"]) # 使用传入的浏览器实例或创建新的 if browser is None: @@ -19,6 +19,7 @@ async def markdown_to_image(markdown_text: str, output_path: str, browser=None): should_close_browser = True page = await browser.newPage() + page.setDefaultNavigationTimeout(15000) # 设置页面样式,使内容更美观 await page.setContent(f""" @@ -122,7 +123,10 @@ async def markdown_to_image(markdown_text: str, output_path: str, browser=None): """) # 等待内容渲染完成 - await asyncio.sleep(0.5) + try: + await asyncio.wait_for(page.waitForNavigation({'waitUntil': 'networkidle0'}), timeout=10) + except Exception: + pass # rendering may already be complete # 获取内容尺寸并设置视口 dimensions = await page.evaluate('''() => {