fix: add chat stop button

Co-authored-by: Nathan Broadbent <ndbroadbent@users.noreply.github.com>
This commit is contained in:
Peter Steinberger
2026-01-25 01:00:15 +00:00
parent 92e794dc18
commit 6a7a1d7085
3 changed files with 101 additions and 3 deletions

View File

@@ -18,6 +18,7 @@ Docs: https://docs.clawd.bot
### Fixes
- BlueBubbles: keep part-index GUIDs in reply tags when short IDs are missing.
- Web UI: hide internal `message_id` hints in chat bubbles.
- Web UI: show Stop button during active runs, swap back to New session when idle. (#1664) Thanks @ndbroadbent.
- Heartbeat: normalize target identifiers for consistent routing.
- TUI: reload history after gateway reconnect to restore session state. (#1663)
- Telegram: use wrapped fetch for long-polling on Node to normalize AbortSignal handling. (#1639)

View File

@@ -0,0 +1,96 @@
import { render } from "lit";
import { describe, expect, it, vi } from "vitest";
import type { SessionsListResult } from "../types";
import { renderChat, type ChatProps } from "./chat";
function createSessions(): SessionsListResult {
return {
ts: 0,
path: "",
count: 0,
defaults: { model: null, contextTokens: null },
sessions: [],
};
}
function createProps(overrides: Partial<ChatProps> = {}): ChatProps {
return {
sessionKey: "main",
onSessionKeyChange: () => undefined,
thinkingLevel: null,
showThinking: false,
loading: false,
sending: false,
canAbort: false,
compactionStatus: null,
messages: [],
toolMessages: [],
stream: null,
streamStartedAt: null,
assistantAvatarUrl: null,
draft: "",
queue: [],
connected: true,
canSend: true,
disabledReason: null,
error: null,
sessions: createSessions(),
focusMode: false,
assistantName: "Clawdbot",
assistantAvatar: null,
onRefresh: () => undefined,
onToggleFocusMode: () => undefined,
onDraftChange: () => undefined,
onSend: () => undefined,
onQueueRemove: () => undefined,
onNewSession: () => undefined,
...overrides,
};
}
describe("chat view", () => {
it("shows a stop button when aborting is available", () => {
const container = document.createElement("div");
const onAbort = vi.fn();
render(
renderChat(
createProps({
canAbort: true,
onAbort,
}),
),
container,
);
const stopButton = Array.from(container.querySelectorAll("button")).find(
(btn) => btn.textContent?.trim() === "Stop",
);
expect(stopButton).not.toBeUndefined();
stopButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
expect(onAbort).toHaveBeenCalledTimes(1);
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();
render(
renderChat(
createProps({
canAbort: false,
onNewSession,
}),
),
container,
);
const newSessionButton = Array.from(container.querySelectorAll("button")).find(
(btn) => btn.textContent?.trim() === "New session",
);
expect(newSessionButton).not.toBeUndefined();
newSessionButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
expect(onNewSession).toHaveBeenCalledTimes(1);
expect(container.textContent).not.toContain("Stop");
});
});

View File

@@ -97,6 +97,7 @@ function renderCompactionIndicator(status: CompactionIndicatorStatus | null | un
export function renderChat(props: ChatProps) {
const canCompose = props.connected;
const isBusy = props.sending || props.stream !== null;
const canAbort = Boolean(props.canAbort && props.onAbort);
const activeSession = props.sessions?.sessions?.find(
(row) => row.key === props.sessionKey,
);
@@ -254,10 +255,10 @@ export function renderChat(props: ChatProps) {
<div class="chat-compose__actions">
<button
class="btn"
?disabled=${!props.connected || props.sending}
@click=${props.onNewSession}
?disabled=${!props.connected || (!canAbort && props.sending)}
@click=${canAbort ? props.onAbort : props.onNewSession}
>
New session
${canAbort ? "Stop" : "New session"}
</button>
<button
class="btn primary"