From d5c42a07ef2e89163e4454ebc66b3f97106ad169 Mon Sep 17 00:00:00 2001 From: chziyue Date: Sat, 4 Apr 2026 00:59:47 +0800 Subject: [PATCH] fix(ui): Stop button shows Send during tool execution (#54528) Merged via squash. Prepared head SHA: f2d65a5c3d0e89087a866fb53739b2ef4848363a Co-authored-by: chziyue <62380760+chziyue@users.noreply.github.com> Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com> Reviewed-by: @velvet-shark --- CHANGELOG.md | 1 + ui/src/ui/views/chat.test.ts | 21 +++++++++++++++++++++ ui/src/ui/views/chat.ts | 2 +- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97be7c6800b..e4866eed529 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ Docs: https://docs.openclaw.ai - Discord/ack reactions: keep automatic ACK reaction auth on the active hydrated Discord account so SecretRef-backed and non-default-account reactions stop falling back to stale default config resolution. (#60081) Thanks @FunJim. - Telegram/model switching: render non-default `/model` callback confirmations with HTML formatting so Telegram shows the selected model in bold instead of raw `**...**` markers. (#60042) Thanks @GitZhangChi. - Plugins/update: allow `openclaw plugins update` to use `--dangerously-force-unsafe-install` for built-in dangerous-code false positives during plugin updates. (#60066) Thanks @huntharo. +- Control UI/chat: keep the Stop button visible during tool-only execution so abortable runs do not fall back to Send while tools are still running. (#54528) thanks @chziyue. ## 2026.4.2 diff --git a/ui/src/ui/views/chat.test.ts b/ui/src/ui/views/chat.test.ts index b6bb9c2f43f..beea4022893 100644 --- a/ui/src/ui/views/chat.test.ts +++ b/ui/src/ui/views/chat.test.ts @@ -689,6 +689,27 @@ describe("chat view", () => { expect(container.textContent).not.toContain("New session"); }); + it("shows a stop button when aborting is available without an active stream", () => { + const container = document.createElement("div"); + render( + renderChat( + createProps({ + canAbort: true, + sending: false, + stream: null, + onAbort: vi.fn(), + }), + ), + container, + ); + + const stopButton = container.querySelector('button[title="Stop"]'); + const sendButton = container.querySelector('button[title="Send"]'); + expect(stopButton).not.toBeNull(); + expect(sendButton).toBeNull(); + expect(container.textContent).not.toContain("New session"); + }); + it("shows a new session button when aborting is unavailable", () => { const container = document.createElement("div"); const onNewSession = vi.fn(); diff --git a/ui/src/ui/views/chat.ts b/ui/src/ui/views/chat.ts index ba93d04bf66..aa8b03eca33 100644 --- a/ui/src/ui/views/chat.ts +++ b/ui/src/ui/views/chat.ts @@ -893,7 +893,7 @@ function renderSlashMenu( export function renderChat(props: ChatProps) { const canCompose = props.connected; - const isBusy = props.sending || props.stream !== null; + const isBusy = props.sending || props.stream !== null || props.canAbort; const canAbort = Boolean(props.canAbort && props.onAbort); const activeSession = props.sessions?.sessions?.find((row) => row.key === props.sessionKey); const reasoningLevel = activeSession?.reasoningLevel ?? "off";