mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-17 02:05:22 -04:00
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>
This commit is contained in:
@@ -24,6 +24,8 @@ import codeRunnerModule from './codeRunner.js';
|
||||
import slashCommands, { initSlashCommands, isCommand, handleSlashCommand, handleSetupInput, handleSetupWizard, typewriterInto } from './slashCommands.js';
|
||||
import createResearchSynapse from './researchSynapse.js';
|
||||
import { createStreamRenderer } from './streamingRenderer.js';
|
||||
import { wireArrowUpRecall, getLastUserMessageFromChatHistory } from './composerArrowUpRecall.js';
|
||||
|
||||
const RESEARCH_TIMEOUT_MS = 360000;
|
||||
const DEFAULT_TIMEOUT_MS = 120000;
|
||||
const RESEARCH_SVG = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>';
|
||||
@@ -217,6 +219,19 @@ import { createStreamRenderer } from './streamingRenderer.js';
|
||||
const ta = document.getElementById('message');
|
||||
if (ta && mod.initSlashAutocomplete) mod.initSlashAutocomplete(ta);
|
||||
}).catch(() => {});
|
||||
|
||||
// ArrowUp on empty composer recalls last user message (like many chat apps).
|
||||
const _wireArrowUpRecall = (composer) =>
|
||||
wireArrowUpRecall(composer, () => getLastUserMessageFromChatHistory(), {
|
||||
autoResize: uiModule?.autoResize,
|
||||
});
|
||||
|
||||
const composer = document.getElementById('message');
|
||||
if (!_wireArrowUpRecall(composer)) {
|
||||
// Init can run before #message exists (templated UI); short retries only.
|
||||
try { requestAnimationFrame(() => _wireArrowUpRecall(document.getElementById('message'))); } catch (_) {}
|
||||
setTimeout(() => _wireArrowUpRecall(document.getElementById('message')), 250);
|
||||
}
|
||||
}
|
||||
|
||||
// addMessage, createMsgFooter, displayMetrics, hideWelcomeScreen, showWelcomeScreen
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user