From 8ffd261bb0cddab2441b79b24daa7e0464cfc7f6 Mon Sep 17 00:00:00 2001 From: ninemine <1371605831@qq.com> Date: Thu, 30 Oct 2025 11:57:46 +0800 Subject: [PATCH] =?UTF-8?q?1.=E4=BF=AE=E6=94=B9ai=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E4=B8=BAmarkdown2.=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E5=86=92=E9=99=A9=E6=94=BE=E5=BC=83=E6=8C=87=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2025-10-29_2_complete-adventure-game.md | 16 +++++ .tasks/2025-10-29_3_ai_chat.md | 62 +++++++++++++++++ games/adventure.py | 69 +++++++++++++++++++ games/base.py | 2 + routers/callback.py | 20 ++++-- 5 files changed, 162 insertions(+), 7 deletions(-) diff --git a/.tasks/2025-10-29_2_complete-adventure-game.md b/.tasks/2025-10-29_2_complete-adventure-game.md index 348a694..20b5c24 100644 --- a/.tasks/2025-10-29_2_complete-adventure-game.md +++ b/.tasks/2025-10-29_2_complete-adventure-game.md @@ -107,6 +107,21 @@ WPSBotGame/ - 阻碍因素:无 - 状态:成功 +## 2025-10-30_00:00:00 +- 已修改: + - games/adventure.py(新增放弃指令分支,新增 `_abandon_adventure`,更新帮助) + - games/base.py(在全局帮助中添加放弃用法) +- 更改: + 1. 新增冒险放弃功能: + - 支持指令:`.adventure abandon`、`.adventure 放弃` + - 结算规则:最低倍率 × 已冒险分钟(向下取整,至少1分钟) + - 发放奖励后删除状态 + 2. 帮助信息更新: + - 本地帮助与全局帮助均加入放弃用法说明 +- 原因:允许用户在冒险过程中主动放弃并按最低倍率获得奖励 +- 阻碍因素:无 +- 状态:成功 + # 详细实施记录 ## 文件修改清单 @@ -205,6 +220,7 @@ state_data = { - ✅ 游戏互斥:冒险期间阻止炼金操作 - ✅ 指令注册:`.adventure` 和 `.冒险` 指令正常工作 - ✅ 帮助信息:显示在全局帮助中 + - ✅ 冒险放弃:`.adventure abandon` / `.adventure 放弃` 按最低倍率结算已冒险分钟并清理状态 ## 代码质量 - ✅ 所有语法检查通过 diff --git a/.tasks/2025-10-29_3_ai_chat.md b/.tasks/2025-10-29_3_ai_chat.md index 1268c86..a055508 100644 --- a/.tasks/2025-10-29_3_ai_chat.md +++ b/.tasks/2025-10-29_3_ai_chat.md @@ -364,3 +364,65 @@ llama-index-llms-ollama>=0.1.0 ## 实施与计划匹配度 实施与计划完全匹配 ✅ + + +## 补充分析:Markdown 渲染与发送通道(2025-10-30) + +### 现状观察 + +- `routers/callback.py` 中仅当 `response_text.startswith('#')` 时才通过 `send_markdown()` 发送,否则使用 `send_text()`。这意味着即使 AI 返回了合法的 Markdown,但不以 `#` 开头(例如代码块、列表、表格、普通段落等),也会被按纯文本通道发送,导致下游(WPS 侧)不进行 Markdown 渲染。 +- `games/ai_chat.py` 的 `_generate_response()` 直接返回 `str(response)`,未对内容类型进行标注或判定,上层仅依赖首字符为 `#` 的启发式判断来选择发送通道。 +- `utils/message.py` 已具备 `send_markdown()` 与 `send_text()` 两种发送方式,对应 `{"msgtype":"markdown"}` 与 `{"msgtype":"text"}` 消息结构;当前缺少自动识别 Markdown 的逻辑。 + +### 影响 + +- 当 AI 返回包含 Markdown 元素但非标题(不以 `#` 开头)的内容时,用户端看到的是未渲染的原始 Markdown 文本,表现为“格式不能成功排版”。 + +### 待确认问题(不含解决方案,需产品/实现口径) + +1. 目标平台(WPS 机器人)对 Markdown 的要求是否仅需 `msgtype=markdown` 即可渲染?是否存在必须以标题开头的限制? +2. 期望策略: + - 是否希望“.ai 的所有回复”统一走 Markdown 通道? + - 还是需要基于 Markdown 特征进行判定(如代码块、列表、链接、表格、行内格式等)? +3. 兼容性:若统一改为 Markdown 通道,是否会影响既有纯文本展示(例如换行、转义、表情)? +4. 其他指令模块是否也可能返回 Markdown?若有,是否一并纳入同一策略? + +### 相关代码参照点(路径) + +- `routers/callback.py`:回复通道选择逻辑(基于 `startswith('#')`) +- `games/ai_chat.py`:AI 回复内容生成与返回(直接返回字符串) +- `utils/message.py`:`send_markdown()` 与 `send_text()` 的消息结构 + + +### 决策结论与范围(2025-10-30) + +- 分支策略:不创建新分支,继续在当前任务上下文内推进。 +- 发送策略:`.ai` 产生的回复统一按 Markdown 发送。 +- 影响范围:仅限 AI 对话功能(`.ai`/`ai_chat`),不扩展到其他指令模块。 + + +# 任务进度(补充) + +## [2025-10-30_??:??:??] 标注 Markdown 渲染问题(记录现状与待确认项) +- 已修改: + - `.tasks/2025-10-29_3_ai_chat.md`:补充“Markdown 渲染与发送通道”分析与待确认清单(仅问题陈述,无解决方案)。 +- 更改: + - 明确当前仅以标题开头触发 Markdown 发送的启发式导致部分 Markdown 未被渲染。 +- 原因: + - 用户反馈“AI 返回内容支持 Markdown,但当前直接当作文本返回导致无法正确排版”。 +- 阻碍因素: + - 目标平台的 Markdown 渲染细节与统一策略选择待确认。 +- 状态: + - 未确认(等待策略口径与平台渲染规范确认)。 + +## [2025-10-30_11:40:31] 执行:AI 回复统一按 Markdown 发送(仅限 AI) +- 已修改: + - `routers/callback.py`:在 `callback_receive()` 的发送阶段,当 `game_type == 'ai_chat'` 且存在 `response_text` 时,无条件调用 `send_markdown(response_text)`;若发送异常,记录日志并回退到 `send_text(response_text)`;其他指令模块继续沿用 `startswith('#')` 的启发式逻辑。 +- 更改: + - 使 `.ai` 产生的回复在 WPS 端稳定触发 Markdown 渲染,不再依赖以 `#` 开头。 +- 原因: + - 对齐“统一按 Markdown 发送(仅限 AI)”的决策,解决 Markdown 文本被当作纯文本发送导致的排版问题。 +- 阻碍因素: + - 暂无。 +- 状态: + - 成功。 \ No newline at end of file diff --git a/games/adventure.py b/games/adventure.py index 93bf66b..b30d180 100644 --- a/games/adventure.py +++ b/games/adventure.py @@ -56,6 +56,10 @@ class AdventureGame(BaseGame): if args in ['help', '帮助', 'info']: return self._get_adventure_help() + # 放弃当前冒险(按最低倍率结算已冒险时间) + if args in ['abandon', '放弃']: + return await self._abandon_adventure(chat_id, user_id) + # 默认:冒险耗时1分钟 else: # 解析消耗时间 @@ -170,6 +174,69 @@ class AdventureGame(BaseGame): return text + async def _abandon_adventure(self, chat_id: int, user_id: int) -> str: + """放弃当前冒险,按最低倍率结算已冒险时间 + + Args: + chat_id: 会话ID(使用0作为用户级标识) + user_id: 用户ID + + Returns: + 放弃结果消息 + """ + try: + # 查询冒险状态 + state = self.db.get_game_state(0, user_id, 'adventure') + if not state: + return "❌ 当前没有进行中的冒险,可使用 `.adventure` 开始新的冒险。" + + state_data = state.get('state_data', {}) + start_time = state_data.get('start_time') + cost_time = state_data.get('cost_time') + if start_time is None or cost_time is None: + # 状态异常,清理并提示 + self.db.delete_game_state(0, user_id, 'adventure') + return "⚠️ 冒险状态异常已清理,请使用 `.adventure` 重新开始。" + + current_time = int(time.time()) + elapsed_seconds = max(0, current_time - int(start_time)) + elapsed_minutes = elapsed_seconds // 60 + if elapsed_minutes < 1: + elapsed_minutes = 1 + + # 计算最低倍率 + try: + min_multiplier = min(m for _, m, _ in self.prize_pool) + except Exception: + # 兜底:若奖池异常,按0.5处理 + min_multiplier = 0.5 + + reward_points = int(min_multiplier * elapsed_minutes) + if reward_points < 0: + reward_points = 0 + + # 发放奖励并清理状态 + if reward_points > 0: + self.db.add_points(user_id, reward_points, "adventure", "冒险放弃奖励") + self.db.delete_game_state(0, user_id, 'adventure') + + # 查询当前积分 + updated_points = self.db.get_user_points(user_id) + + # 输出 + text = f"## ⚡️ 冒险放弃\n\n" + text += f"**已计入时间**: {elapsed_minutes} 分钟\n\n" + text += f"**最低倍率**: {min_multiplier} 倍\n\n" + text += f"**获得积分**: {reward_points} 分\n\n" + text += f"**当前积分**: {updated_points['points']} 分\n\n" + text += "---\n\n" + text += "💡 提示:可随时使用 `.adventure` 再次踏上冒险之旅!" + return text + except Exception as e: + logger.error(f"放弃冒险时出错: {e}", exc_info=True) + # 失败时不影响原状态,返回提示 + return f"❌ 放弃冒险失败:{str(e)}" + def _draw_prize(self, prize_pool: list) -> dict: """从奖品池中抽取奖品 @@ -209,6 +276,8 @@ class AdventureGame(BaseGame): text += f"- `.adventure time` - 消耗time分钟进行冒险, 最少一分钟\n" text += f"### 其他功能\n" + text += f"- `.adventure abandon` - 放弃当前冒险,按最低倍率结算已冒险时间\n" + text += f"- `.adventure 放弃` - 放弃当前冒险,按最低倍率结算已冒险时间\n" text += f"- `.adventure help` - 查看帮助\n\n" return text diff --git a/games/base.py b/games/base.py index 8deaa0c..9b4cde1 100644 --- a/games/base.py +++ b/games/base.py @@ -100,6 +100,8 @@ def get_help_message() -> str: - `.adventure` - 消耗1分钟进行冒险 - `.冒险` - 消耗1分钟进行冒险 - `.adventure 5` - 消耗5分钟进行冒险 +- `.adventure abandon` - 放弃当前冒险,按最低倍率结算已冒险时间 +- `.adventure 放弃` - 放弃当前冒险,按最低倍率结算已冒险时间 - `.adventure help` - 查看冒险帮助 ### 🎁 积分赠送系统 diff --git a/routers/callback.py b/routers/callback.py index 56b0994..65e0b67 100644 --- a/routers/callback.py +++ b/routers/callback.py @@ -76,14 +76,20 @@ async def callback_receive(request: Request): # 发送回复 if response_text: sender = get_message_sender() - - # 根据内容选择消息类型 - if response_text.startswith('#'): - # Markdown格式 - await sender.send_markdown(response_text) + + # AI 对话:统一按 Markdown 发送(按任务决策) + if game_type == 'ai_chat': + try: + await sender.send_markdown(response_text) + except Exception as send_md_err: + logger.error(f"发送Markdown消息失败,改用文本发送: {send_md_err}") + await sender.send_text(response_text) else: - # 普通文本 - await sender.send_text(response_text) + # 其他模块保持原有启发式:以 # 开头视为 Markdown,否则文本 + if response_text.startswith('#'): + await sender.send_markdown(response_text) + else: + await sender.send_text(response_text) return JSONResponse({"result": "ok"})