fix: make chat.send non-blocking

This commit is contained in:
Peter Steinberger
2026-01-10 17:02:20 +01:00
parent b99eb4c9f3
commit d781508952
8 changed files with 347 additions and 49 deletions

View File

@@ -201,6 +201,7 @@ export type AppViewState = {
handleWhatsAppLogout: () => Promise<void>;
handleTelegramSave: () => Promise<void>;
handleSendChat: (messageOverride?: string, opts?: { restoreDraft?: boolean }) => Promise<void>;
handleAbortChat: () => Promise<void>;
removeQueuedMessage: (id: string) => void;
resetToolStream: () => void;
handleLogsScroll: (event: Event) => void;
@@ -493,11 +494,13 @@ export function renderApp(state: AppViewState) {
...state.settings,
useNewChatLayout: !state.settings.useNewChatLayout,
}),
onDraftChange: (next) => (state.chatMessage = next),
onSend: () => state.handleSendChat(),
onQueueRemove: (id) => state.removeQueuedMessage(id),
onNewSession: () =>
state.handleSendChat("/new", { restoreDraft: true }),
onDraftChange: (next) => (state.chatMessage = next),
onSend: () => state.handleSendChat(),
canAbort: Boolean(state.chatRunId),
onAbort: () => void state.handleAbortChat(),
onQueueRemove: (id) => state.removeQueuedMessage(id),
onNewSession: () =>
state.handleSendChat("/new", { restoreDraft: true }),
// Sidebar props for tool output viewing
sidebarOpen: state.sidebarOpen,
sidebarContent: state.sidebarContent,

View File

@@ -52,6 +52,7 @@ import {
import {
loadChatHistory,
sendChatMessage,
abortChatRun,
handleChatEvent,
type ChatEventPayload,
} from "./controllers/chat";
@@ -1028,6 +1029,20 @@ export class ClawdbotApp extends LitElement {
return this.chatSending || Boolean(this.chatRunId);
}
private isChatStopCommand(text: string) {
const trimmed = text.trim();
if (!trimmed) return false;
const normalized = trimmed.toLowerCase();
return normalized === "/stop" || normalized === "stop" || normalized === "abort";
}
async handleAbortChat() {
if (!this.connected) return;
this.chatMessage = "";
if (!this.chatRunId) return;
await abortChatRun(this);
}
private enqueueChatMessage(text: string) {
const trimmed = text.trim();
if (!trimmed) return;
@@ -1053,14 +1068,6 @@ export class ClawdbotApp extends LitElement {
if (ok) {
this.setLastActiveSessionKey(this.sessionKey);
}
if (ok && this.chatRunId) {
// chat.send returned (run finished), but we missed the chat final event.
this.chatRunId = null;
this.chatStream = null;
this.chatStreamStartedAt = null;
this.resetToolStream();
void loadChatHistory(this);
}
if (ok && opts?.restoreDraft && opts.previousDraft?.trim()) {
this.chatMessage = opts.previousDraft;
}
@@ -1095,6 +1102,11 @@ export class ClawdbotApp extends LitElement {
const message = (messageOverride ?? this.chatMessage).trim();
if (!message) return;
if (this.isChatStopCommand(message)) {
await this.handleAbortChat();
return;
}
if (messageOverride == null) {
this.chatMessage = "";
}

View File

@@ -92,6 +92,22 @@ export async function sendChatMessage(state: ChatState, message: string): Promis
}
}
export async function abortChatRun(state: ChatState): Promise<boolean> {
if (!state.client || !state.connected) return false;
const runId = state.chatRunId;
if (!runId) return false;
try {
await state.client.request("chat.abort", {
sessionKey: state.sessionKey,
runId,
});
return true;
} catch (err) {
state.lastError = String(err);
return false;
}
}
export function handleChatEvent(
state: ChatState,
payload?: ChatEventPayload,

View File

@@ -23,6 +23,7 @@ export type ChatProps = {
thinkingLevel: string | null;
loading: boolean;
sending: boolean;
canAbort?: boolean;
messages: unknown[];
toolMessages: unknown[];
stream: string | null;
@@ -52,6 +53,7 @@ export type ChatProps = {
onToggleLayout?: () => void;
onDraftChange: (next: string) => void;
onSend: () => void;
onAbort?: () => void;
onQueueRemove: (id: string) => void;
onNewSession: () => void;
onOpenSidebar?: (content: string) => void;
@@ -61,7 +63,7 @@ export type ChatProps = {
export function renderChat(props: ChatProps) {
const canCompose = props.connected;
const isBusy = props.sending || Boolean(props.stream);
const isBusy = props.sending || props.stream !== null;
const activeSession = props.sessions?.sessions?.find(
(row) => row.key === props.sessionKey,
);
@@ -222,6 +224,17 @@ export function renderChat(props: ChatProps) {
>
New session
</button>
${props.onAbort
? html`
<button
class="btn danger"
?disabled=${!props.connected || !isBusy || props.canAbort === false}
@click=${props.onAbort}
>
Stop
</button>
`
: nothing}
<button
class="btn primary"
?disabled=${!props.connected}