Files
odysseus/static/js/composerArrowUpRecall.js
T
Mostafa Eid d6882a895e feat(chat): recall last user message on empty composer ArrowUp (#1175)
Pressing ArrowUp on an empty #message composer restores the last sent user text, matching common chat-app UX (Slack, Discord, ChatGPT).

- Read from #chat-history .msg-user dataset.raw (same path as resend/regenerate), not session sidebar metadata

- Literal empty check (whitespace-only drafts are preserved); ignore Shift/Alt/Ctrl/Meta and IME composition

- Extract wiring to composerArrowUpRecall.js; rAF + 250ms retry only (no global MutationObserver)

- Add tests/test_composer_arrow_up_recall_js.py

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 13:06:05 +02:00

62 lines
1.9 KiB
JavaScript

/**
* ArrowUp on an empty composer recalls the last user message (chat-app convention).
*/
/**
* Last user bubble in the active chat surface (#chat-history), using dataset.raw
* (same source as resend/regenerate in chat.js).
*
* @param {Document | Element} [root=document]
* @returns {string}
*/
export function getLastUserMessageFromChatHistory(root = document) {
const chatBox =
root && root.id === 'chat-history' && typeof root.querySelectorAll === 'function'
? root
: (root.getElementById ? root.getElementById('chat-history') : null);
if (!chatBox) return '';
const users = chatBox.querySelectorAll('.msg-user');
const last = users[users.length - 1];
if (!last) return '';
const bodyEl = last.querySelector('.body');
return last.dataset?.raw || (bodyEl ? bodyEl.textContent : '') || '';
}
/**
* @param {HTMLTextAreaElement} composer
* @param {() => string} getLastUserMessage
* @param {{ autoResize?: (el: HTMLTextAreaElement) => void }} [options]
* @returns {boolean} true when wired (or already wired)
*/
export function wireArrowUpRecall(composer, getLastUserMessage, options = {}) {
if (!composer) return false;
if (composer._arrowUpRecallWired) return true;
composer._arrowUpRecallWired = true;
const { autoResize } = options;
composer.addEventListener('keydown', (e) => {
// Only ArrowUp, no modifier keys, no IME composition
if (e.key !== 'ArrowUp') return;
if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey) return;
if (e.isComposing) return;
// Literal emptiness — intentional whitespace is not empty
if (composer.value !== '') return;
const recalled = getLastUserMessage();
if (!recalled) return;
e.preventDefault();
composer.value = recalled;
try {
composer.selectionStart = composer.selectionEnd = recalled.length;
} catch (_) {}
if (autoResize) autoResize(composer);
});
return true;
}