From 6a9357c51d85294ce140b99cfef7f46103106725 Mon Sep 17 00:00:00 2001 From: Clawdbot Date: Wed, 28 Jan 2026 15:37:36 +0100 Subject: [PATCH] feat(ui): Escape key to stop generation + Delete session from Chat tab Two quality-of-life improvements for the Chat tab: 1. **Escape to Stop**: Press Escape during generation to stop (same as Stop button) - Also shows 'Esc' hint on the Stop button - Escape closes delete confirmation if open 2. **Delete Session**: Delete button in Chat tab with confirmation modal - Shows 'Delete this session?' modal - Cancel/Delete buttons - Enter = Delete, Escape = Cancel - Button disabled during generation Changes: - ui/src/ui/views/chat.ts: Add keydown handler, delete modal, delete button - ui/src/ui/app.ts: Add chatDeleteConfirm state - ui/src/ui/app-render.ts: Wire up delete props - ui/src/styles/components.css: Styles for delete confirmation modal --- ui/src/styles/components.css | 49 ++++++++++++++++++++++++ ui/src/ui/app-render.ts | 9 +++++ ui/src/ui/app.ts | 1 + ui/src/ui/views/chat.ts | 72 +++++++++++++++++++++++++++++++++++- 4 files changed, 129 insertions(+), 2 deletions(-) diff --git a/ui/src/styles/components.css b/ui/src/styles/components.css index 27dfe62d1..3818415d5 100644 --- a/ui/src/styles/components.css +++ b/ui/src/styles/components.css @@ -1484,3 +1484,52 @@ flex-wrap: wrap; gap: 8px; } + +/* Chat delete confirmation modal */ +.chat-delete-overlay { + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.8); + backdrop-filter: blur(4px); + display: flex; + align-items: center; + justify-content: center; + padding: 24px; + z-index: 100; + border-radius: var(--radius-lg); +} + +.chat-delete-card { + width: min(400px, 100%); + background: var(--card); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + padding: 20px; + text-align: center; + animation: scale-in 0.15s var(--ease-out); +} + +.chat-delete-title { + font-size: 16px; + font-weight: 600; + margin-bottom: 8px; +} + +.chat-delete-sub { + color: var(--muted); + font-size: 13px; + margin-bottom: 16px; + word-break: break-word; +} + +.chat-delete-actions { + display: flex; + justify-content: center; + gap: 8px; +} + +.chat-delete-hint { + margin-top: 12px; + font-size: 11px; + color: var(--muted); +} diff --git a/ui/src/ui/app-render.ts b/ui/src/ui/app-render.ts index a088c33ff..5191d9f63 100644 --- a/ui/src/ui/app-render.ts +++ b/ui/src/ui/app-render.ts @@ -496,6 +496,15 @@ export function renderApp(state: AppViewState) { onSplitRatioChange: (ratio: number) => state.handleSplitRatioChange(ratio), assistantName: state.assistantName, assistantAvatar: state.assistantAvatar, + // Delete session + showDeleteConfirm: state.chatDeleteConfirm, + onDeleteClick: () => (state.chatDeleteConfirm = true), + onDeleteConfirm: async () => { + state.chatDeleteConfirm = false; + const { deleteSession } = await import("./controllers/sessions"); + await deleteSession(state as Parameters[0], state.sessionKey); + }, + onDeleteCancel: () => (state.chatDeleteConfirm = false), }) : nothing} diff --git a/ui/src/ui/app.ts b/ui/src/ui/app.ts index d23e543cd..67e2ef048 100644 --- a/ui/src/ui/app.ts +++ b/ui/src/ui/app.ts @@ -128,6 +128,7 @@ export class MoltbotApp extends LitElement { @state() compactionStatus: import("./app-tool-stream").CompactionStatus | null = null; @state() chatAvatarUrl: string | null = null; @state() chatThinkingLevel: string | null = null; + @state() chatDeleteConfirm = false; @state() chatQueue: ChatQueueItem[] = []; @state() chatAttachments: ChatAttachment[] = []; // Sidebar state for tool output viewing diff --git a/ui/src/ui/views/chat.ts b/ui/src/ui/views/chat.ts index f5fb6e80b..cc03aec66 100644 --- a/ui/src/ui/views/chat.ts +++ b/ui/src/ui/views/chat.ts @@ -68,6 +68,11 @@ export type ChatProps = { onCloseSidebar?: () => void; onSplitRatioChange?: (ratio: number) => void; onChatScroll?: (event: Event) => void; + // Delete session + showDeleteConfirm?: boolean; + onDeleteClick?: () => void; + onDeleteConfirm?: () => void; + onDeleteCancel?: () => void; }; const COMPACTION_TOAST_DURATION_MS = 5000; @@ -240,7 +245,58 @@ export function renderChat(props: ChatProps) { `; return html` -
+
{ + if (e.key === "Escape") { + e.preventDefault(); + // If delete confirm is open, cancel it + if (props.showDeleteConfirm && props.onDeleteCancel) { + props.onDeleteCancel(); + return; + } + // Otherwise, if we can abort, do it + if (canAbort && props.onAbort) { + props.onAbort(); + } + } + // Enter in delete confirm modal = confirm delete + if (e.key === "Enter" && props.showDeleteConfirm && props.onDeleteConfirm) { + e.preventDefault(); + props.onDeleteConfirm(); + } + }} + > + ${props.showDeleteConfirm + ? html` + + ` + : nothing} + ${props.disabledReason ? html`
${props.disabledReason}
` : nothing} @@ -359,8 +415,20 @@ export function renderChat(props: ChatProps) { ?disabled=${!props.connected || (!canAbort && props.sending)} @click=${canAbort ? props.onAbort : props.onNewSession} > - ${canAbort ? "Stop" : "New session"} + ${canAbort ? "Stop" : "New session"}${canAbort ? "Esc" : ""} + ${props.onDeleteClick + ? html` + + ` + : nothing}