From e28d871940fb3e7e013d561802b53aeb79161dc8 Mon Sep 17 00:00:00 2001 From: "Mr.Xia" <1424473282@qq.com> Date: Sat, 9 May 2026 23:48:54 +0800 Subject: [PATCH] =?UTF-8?q?fix(chatai):=20=E5=AE=89=E5=85=A8=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D+=E4=BB=A3=E7=A0=81=E8=B4=A8=E9=87=8F=E6=94=B9?= =?UTF-8?q?=E8=BF=9B=20-=20=5Fforce=5Fkill=5Fchrome:=20=E4=BB=85kill?= =?UTF-8?q?=E5=B8=A6--remote-debugging-port=E7=9A=84headless=20chrome=20-?= =?UTF-8?q?=20AI=20API:=20=E6=B7=BB=E5=8A=A060s=20timeout=20+=20run=5Fin?= =?UTF-8?q?=5Fexecutor=E9=81=BF=E5=85=8D=E9=98=BB=E5=A1=9E=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6=E5=BE=AA=E7=8E=AF=20-=20AI=E7=B3=BB=E7=BB=9F=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=E6=8A=BD=E5=8F=96=E4=B8=BA=E5=B8=B8=E9=87=8F=20-=20ma?= =?UTF-8?q?rkdown=E8=BD=AC=E5=9B=BE=E7=89=87:=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E7=9A=84html.escape=E5=89=8D=E7=BD=AE=20-=20?= =?UTF-8?q?screenshot:=20=E7=AD=89=E5=BE=85=E6=B8=B2=E6=9F=93=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E6=9B=BF=E4=BB=A3=E5=9B=BA=E5=AE=9Asleep=20-=20?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E4=BF=A1=E6=81=AF=E4=B8=8D=E5=86=8D=E6=9A=B4?= =?UTF-8?q?=E9=9C=B2=E5=BC=82=E5=B8=B8=E8=AF=A6=E6=83=85=E7=BB=99=E7=94=A8?= =?UTF-8?q?=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- danding_bot/plugins/chatai/__init__.py | 46 ++++++++++++++---------- danding_bot/plugins/chatai/screenshot.py | 12 ++++--- 2 files changed, 36 insertions(+), 22 deletions(-) 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('''() => {