From c7ef7e715c1d35da66cd8f2dc3b67c9eda59b6f2 Mon Sep 17 00:00:00 2001 From: qing <1776177662@qq.com> Date: Sat, 1 Aug 2026 14:12:27 +0800 Subject: [PATCH] =?UTF-8?q?fix(botops):=20v1.3=20-=20=E9=80=82=E9=85=8D?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E7=AB=AF=E4=B8=AD=E6=96=AD=E6=9C=BA=E5=88=B6?= =?UTF-8?q?=EF=BC=8CCtrl+C=E7=AB=8B=E5=8D=B3=E5=8F=AF=E9=9D=A0=E5=81=9C?= =?UTF-8?q?=E6=AD=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题:收到interrupt_ack时仅显示系统通知,没有立即收尾前端状态: - 思考区域一直停留在"💭 思考中..."状态,不会更新 - AI回复状态未重置,后续残留chunk可能继续追加 - 服务端不发送reasoning_end,思考区域无法正常结束 修复: - ChatView新增abort_reasoning()方法:中断时将思考区域标签更新为"🛑 思考已中断"(保留已展示内容) - append_reasoning()增加_ai_finalized检查,中断后丢弃残留思考片段,防止重新创建思考区域 - app.py收到interrupt_ack时:先abort_reasoning()更新思考区域,再finish_ai()收尾AI状态,最后显示系统通知 - 中断后服务端发来的残留chunk/reasoning/complete均被安全忽略(append_ai已有_finalized检查,finish_ai幂等) - 更新README v1.3日志 --- BotOps/README.md | 7 +++++++ BotOps/app.py | 3 +++ BotOps/widgets/chat_view.py | 14 ++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/BotOps/README.md b/BotOps/README.md index 938f9c6..54e5948 100644 --- a/BotOps/README.md +++ b/BotOps/README.md @@ -71,6 +71,13 @@ BotOps/ ## 更新日志 +### v1.3 +- ✅ **适配服务端中断机制(Ctrl+C可靠停止)**: + - 收到 `interrupt_ack` 时立即收尾前端状态:调用 `abort_reasoning()` 将思考区域更新为"🛑 思考已中断"标签,调用 `finish_ai()` 结束AI回复状态 + - 新增 `ChatView.abort_reasoning()` 方法:中断时更新思考区域标签(保留已展示的思考内容) + - `append_reasoning()` 增加 `_ai_finalized` 检查,中断后丢弃残留的思考片段防止重新创建思考区域 + - 中断后服务端发来的残留 `chunk`/`reasoning`/`complete` 消息均被安全忽略,不会污染UI + ### v1.2 - ✅ **彻底修复思考区域顺序问题**:移除预创建空 AI widget 的逻辑,改为第一次收到 `chunk` 时才创建 widget。按消息到达顺序自然挂载(`reasoning_start`→`reasoning`→`chunk`),从根本上保证思考区域始终在正式回答之前 - ✅ 简化 `complete` 处理逻辑:仅作为收尾重置状态信号,不再进行补建逻辑 diff --git a/BotOps/app.py b/BotOps/app.py index 5ea6049..ca3170f 100644 --- a/BotOps/app.py +++ b/BotOps/app.py @@ -226,6 +226,9 @@ class BotOpsApp(App): f"❌ [{data.get('task_id', '?')}] {data.get('task_title', '?')}: " f"{data.get('error', '未知错误')}") elif t == "interrupt_ack": + # 服务端确认中断:立即收尾前端状态(更新思考区域为已中断、结束AI回复) + await self.chat.abort_reasoning() + await self.chat.finish_ai("") await self.notify_system(f"🛑 {content}") elif t == "error": await self.notify_system(f"❌ {content}", level="error") diff --git a/BotOps/widgets/chat_view.py b/BotOps/widgets/chat_view.py index 2dcf3a4..09c0bf8 100644 --- a/BotOps/widgets/chat_view.py +++ b/BotOps/widgets/chat_view.py @@ -75,6 +75,9 @@ class ChatView(VerticalScroll): """追加思考内容增量。""" if not delta: return + if self._ai_finalized: + # 已收尾(中断/完成),丢弃残留的思考片段 + return if self._reasoning_widget is None: await self.start_reasoning() self._reasoning_text += delta @@ -95,6 +98,17 @@ class ChatView(VerticalScroll): self._reasoning_widget.update(f"💭 思考过程({len(self._reasoning_text)}字符)\n{display}\n{'─' * 30}") # 思考区域保留显示,不删除 + async def abort_reasoning(self): + """中断思考区域(用户Ctrl+C),更新提示文字为已中断。""" + if self._reasoning_widget is not None: + if self._reasoning_text: + display = self._reasoning_text + if len(display) > 3000: + display = "...(前略)\n" + display[-3000:] + self._reasoning_widget.update(f"🛑 思考已中断({len(self._reasoning_text)}字符)\n{display}") + else: + self._reasoning_widget.update("🛑 思考已中断") + async def _ensure_ai_widget(self): """确保AI widget存在(第一次chunk到来时创建)。""" if self._ai_widget is None: