feat(site): add keyboard shortcuts to agents page (#22417)

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
This commit is contained in:
Kyle Carberry
2026-02-27 17:33:43 -05:00
committed by GitHub
parent 12083441e0
commit 5fb644a6cd
3 changed files with 46 additions and 0 deletions
@@ -397,6 +397,9 @@ export const AgentChatInput = memo<AgentChatInputProps>(
} else if (isEditingHistoryMessage) {
e.preventDefault();
handleCancelHistoryEdit();
} else if (isStreaming && onInterrupt && !isInterruptPending) {
e.preventDefault();
onInterrupt();
}
return;
}
@@ -422,6 +425,9 @@ export const AgentChatInput = memo<AgentChatInputProps>(
handleSubmit,
input,
isEditingHistoryMessage,
isInterruptPending,
isStreaming,
onInterrupt,
onPromoteQueuedMessage,
queuedMessages,
],
+5
View File
@@ -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);
@@ -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]);
}