mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-23 16:36:23 +00:00
fix(desktop): split steer and queue keyboard shortcuts (#69797)
Keep Enter as the Cursor-style live-turn steering gesture and assign Ctrl/Cmd+Enter to queue the current draft. Align keybind settings, tooltips, translations, and the Electron queue-boundary coverage.
This commit is contained in:
parent
2ebeede006
commit
406d7a67f0
7 changed files with 33 additions and 21 deletions
|
|
@ -26,12 +26,18 @@ async function send(page: Page, text: string): Promise<void> {
|
|||
|
||||
async function steer(page: Page, text: string): Promise<void> {
|
||||
const composer = page.locator('[contenteditable="true"]').first()
|
||||
const primary = page.locator('[data-slot="composer-root"] button[type="submit"]')
|
||||
|
||||
await composer.click()
|
||||
await composer.type(text, { delay: 5 })
|
||||
await expect(primary).toHaveAttribute('aria-label', /Steer/)
|
||||
await primary.click()
|
||||
await page.keyboard.press('Enter')
|
||||
}
|
||||
|
||||
async function queue(page: Page, text: string): Promise<void> {
|
||||
const composer = page.locator('[contenteditable="true"]').first()
|
||||
|
||||
await composer.click()
|
||||
await composer.type(text, { delay: 5 })
|
||||
await page.keyboard.press('Control+Enter')
|
||||
}
|
||||
|
||||
async function transcriptMessageOrder(page: Page): Promise<string[]> {
|
||||
|
|
@ -72,15 +78,10 @@ test.describe('queued prompt turn boundary', () => {
|
|||
|
||||
test('submits a queued prompt only after the active turn completes', async () => {
|
||||
const { mock, page } = fixture!
|
||||
const composer = page.locator('[contenteditable="true"]').first()
|
||||
const queue = page.locator('[data-slot="composer-root"] button[aria-label="Queue message"]')
|
||||
|
||||
await send(page, ACTIVE_PROMPT)
|
||||
await mock.waitForHeldStream()
|
||||
|
||||
await composer.click()
|
||||
await composer.type(QUEUED_PROMPT, { delay: 5 })
|
||||
await queue.click()
|
||||
await queue(page, QUEUED_PROMPT)
|
||||
await expect(page.getByText('1 Queued')).toBeVisible()
|
||||
|
||||
// The mock keeps the active SSE stream open, so a queued prompt has no
|
||||
|
|
|
|||
|
|
@ -65,15 +65,15 @@ describe('ComposerControls shortcut tooltips', () => {
|
|||
await expectShortcutTooltip('Send', '↵')
|
||||
})
|
||||
|
||||
it('shows Ctrl+Enter for Steer', async () => {
|
||||
it('shows Enter for Steer', async () => {
|
||||
renderControls({ busy: true, busyAction: 'steer' })
|
||||
|
||||
await expectShortcutTooltip('Steer the current run', 'Ctrl+↵')
|
||||
await expectShortcutTooltip('Steer the current run', '↵')
|
||||
})
|
||||
|
||||
it('shows Enter for Queue', async () => {
|
||||
it('shows Ctrl+Enter for Queue', async () => {
|
||||
renderControls({ busy: true, busyAction: 'queue' })
|
||||
|
||||
await expectShortcutTooltip('Queue message', '↵')
|
||||
await expectShortcutTooltip('Queue message', 'Ctrl+↵')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ export function ComposerControls({
|
|||
<DictationButton disabled={disabled} onToggle={onDictate} state={state.voice} status={voiceStatus} />
|
||||
<AutoSpeakButton active={autoSpeak} disabled={disabled} onToggle={onToggleAutoSpeak} />
|
||||
{busyAction === 'steer' ? (
|
||||
<Tip label={<TipKeybindLabel actionId="composer.send" text={c.queueMessage} />}>
|
||||
<Tip label={<TipKeybindLabel actionId="composer.queue" text={c.queueMessage} />}>
|
||||
<Button
|
||||
aria-label={c.queueMessage}
|
||||
className={GHOST_ICON_BTN}
|
||||
|
|
@ -116,7 +116,7 @@ export function ComposerControls({
|
|||
label={
|
||||
busy ? (
|
||||
<TipKeybindLabel
|
||||
actionId={busyAction === 'steer' ? 'composer.steer' : 'composer.send'}
|
||||
actionId={busyAction === 'steer' ? 'composer.steer' : busyAction === 'queue' ? 'composer.queue' : 'composer.send'}
|
||||
text={busyLabel}
|
||||
/>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -587,14 +587,22 @@ export function ChatBar({
|
|||
return
|
||||
}
|
||||
|
||||
// Cmd/Ctrl+Enter is reserved for steering the live run — never a send.
|
||||
// Steer when there's a steerable draft, otherwise swallow it so it can't
|
||||
// surprise-send. (Plain Enter still queues while busy / sends when idle.)
|
||||
// Cmd/Ctrl+Enter queues a follow-up while a turn runs. Plain Enter steers
|
||||
// a text-only draft, so both live-turn actions stay reachable by keyboard.
|
||||
if (event.key === 'Enter' && (event.metaKey || event.ctrlKey) && !event.shiftKey) {
|
||||
event.preventDefault()
|
||||
|
||||
if (canSteer) {
|
||||
steerDraft()
|
||||
if (busy && !disabled) {
|
||||
// As with plain Enter, source the just-typed content from the DOM so a
|
||||
// fast keypress cannot queue a stale draft.
|
||||
const editorText = editorRef.current ? composerPlainText(editorRef.current) : draftRef.current
|
||||
|
||||
if (editorText !== draftRef.current) {
|
||||
draftRef.current = editorText
|
||||
setComposerText(editorText)
|
||||
}
|
||||
|
||||
queueDraft()
|
||||
}
|
||||
|
||||
return
|
||||
|
|
|
|||
|
|
@ -292,6 +292,7 @@ export const en: Translations = {
|
|||
'composer.send': 'Send message',
|
||||
'composer.newline': 'Insert newline',
|
||||
'composer.steer': 'Steer the running turn',
|
||||
'composer.queue': 'Queue message',
|
||||
'composer.sendQueued': 'Send next queued turn',
|
||||
'composer.mention': 'Reference files, folders, URLs',
|
||||
'composer.slash': 'Slash command palette',
|
||||
|
|
|
|||
|
|
@ -283,6 +283,7 @@ export const zh: Translations = {
|
|||
'composer.send': '发送消息',
|
||||
'composer.newline': '插入换行',
|
||||
'composer.steer': '引导正在运行的回合',
|
||||
'composer.queue': '消息排队',
|
||||
'composer.sendQueued': '发送下一条排队消息',
|
||||
'composer.mention': '引用文件、文件夹、网址',
|
||||
'composer.slash': '斜杠命令面板',
|
||||
|
|
|
|||
|
|
@ -197,7 +197,8 @@ export interface KeybindReadonly {
|
|||
export const KEYBIND_READONLY: readonly KeybindReadonly[] = [
|
||||
{ id: 'composer.send', category: 'composer', keys: ['enter'] },
|
||||
{ id: 'composer.newline', category: 'composer', keys: ['shift+enter'] },
|
||||
{ id: 'composer.steer', category: 'composer', keys: ['mod+enter'] },
|
||||
{ id: 'composer.steer', category: 'composer', keys: ['enter'] },
|
||||
{ id: 'composer.queue', category: 'composer', keys: ['mod+enter'] },
|
||||
{ id: 'composer.sendQueued', category: 'composer', keys: ['mod+shift+k'] },
|
||||
{ id: 'composer.mention', category: 'composer', keys: ['@'] },
|
||||
{ id: 'composer.slash', category: 'composer', keys: ['/'] },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue