From 5fb644a6cd688b8218abca759cc9408c094ad829 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Fri, 27 Feb 2026 17:33:43 -0500 Subject: [PATCH] feat(site): add keyboard shortcuts to agents page (#22417) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds two keyboard shortcuts to the agents page: - **Escape** — Interrupts the running agent when viewing a chat detail page. Only fires when focus is outside text inputs/textareas so it doesn't conflict with the existing edit-cancel Escape handler in the chat input. - **Ctrl+N / Cmd+N** — Navigates to create a new agent. Also skipped when focus is in a text input/textarea. Both keybindings are implemented in a new `useAgentsPageKeybindings.ts` hook file: - `useAgentsPageKeybindings` — used in `AgentsPage.tsx` for Ctrl+N - `useAgentDetailKeybindings` — used in `AgentDetail.tsx` for Escape → interrupt --- site/src/pages/AgentsPage/AgentChatInput.tsx | 6 ++++ site/src/pages/AgentsPage/AgentsPage.tsx | 5 +++ .../AgentsPage/useAgentsPageKeybindings.ts | 35 +++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 site/src/pages/AgentsPage/useAgentsPageKeybindings.ts diff --git a/site/src/pages/AgentsPage/AgentChatInput.tsx b/site/src/pages/AgentsPage/AgentChatInput.tsx index 74421f0826..b07e99f677 100644 --- a/site/src/pages/AgentsPage/AgentChatInput.tsx +++ b/site/src/pages/AgentsPage/AgentChatInput.tsx @@ -397,6 +397,9 @@ export const AgentChatInput = memo( } else if (isEditingHistoryMessage) { e.preventDefault(); handleCancelHistoryEdit(); + } else if (isStreaming && onInterrupt && !isInterruptPending) { + e.preventDefault(); + onInterrupt(); } return; } @@ -422,6 +425,9 @@ export const AgentChatInput = memo( handleSubmit, input, isEditingHistoryMessage, + isInterruptPending, + isStreaming, + onInterrupt, onPromoteQueuedMessage, queuedMessages, ], diff --git a/site/src/pages/AgentsPage/AgentsPage.tsx b/site/src/pages/AgentsPage/AgentsPage.tsx index 0ade6e5e2e..423260d599 100644 --- a/site/src/pages/AgentsPage/AgentsPage.tsx +++ b/site/src/pages/AgentsPage/AgentsPage.tsx @@ -52,6 +52,7 @@ import { getModelSelectorPlaceholder, hasConfiguredModelsInCatalog, } from "./modelOptions"; +import { useAgentsPageKeybindings } from "./useAgentsPageKeybindings"; const emptyInputStorageKey = "agents.empty-input"; const selectedWorkspaceIdStorageKey = "agents.selected-workspace-id"; @@ -338,6 +339,10 @@ const AgentsPage: FC = () => { document.title = pageTitle("Agents"); }, []); + useAgentsPageKeybindings({ + onNewAgent: handleNewAgent, + }); + useEffect(() => { if (!agentId) { setIsRightPanelOpen(false); diff --git a/site/src/pages/AgentsPage/useAgentsPageKeybindings.ts b/site/src/pages/AgentsPage/useAgentsPageKeybindings.ts new file mode 100644 index 0000000000..e6b72ddee6 --- /dev/null +++ b/site/src/pages/AgentsPage/useAgentsPageKeybindings.ts @@ -0,0 +1,35 @@ +import { useEffect } from "react"; + +/** + * Global keyboard shortcuts for the Agents page. + * + * - Ctrl+N / Cmd+N — Create a new agent. + */ +export function useAgentsPageKeybindings({ + onNewAgent, +}: { + onNewAgent: () => void; +}) { + useEffect(() => { + const handler = (event: KeyboardEvent) => { + // Ignore events originating from inputs / textareas / contenteditable + // so we don't hijack normal typing. + const target = event.target as HTMLElement | null; + if (target) { + const tag = target.tagName; + if (tag === "INPUT" || tag === "TEXTAREA" || target.isContentEditable) { + return; + } + } + + // Ctrl+N / Cmd+N — new agent + if (event.key === "n" && (event.metaKey || event.ctrlKey)) { + event.preventDefault(); + onNewAgent(); + } + }; + + document.addEventListener("keydown", handler); + return () => document.removeEventListener("keydown", handler); + }, [onNewAgent]); +}