@@ -7,13 +7,13 @@ import { useSettingsStore } from '../stores/settings'
77import { sendWsMessage , notifyUserDraftUpdated } from ' ../composables/useWebSocket'
88import { useVisualViewport } from ' ../composables/useVisualViewport'
99import { isSupportedMimeType , MAX_FILE_SIZE , SUPPORTED_IMAGE_TYPES , draftMediaToMediaItem } from ' ../utils/fileUtils'
10- import { getParsedContent } from ' ../utils/parsedContent'
1110import { toast } from ' ../composables/useToast'
1211import { PERMISSION_MODE , PERMISSION_MODE_LABELS , PERMISSION_MODE_DESCRIPTIONS , MODEL , MODEL_LABELS , EFFORT , EFFORT_LABELS , EFFORT_DISPLAY_LABELS , THINKING_LABELS , THINKING_DISPLAY_LABELS , CLAUDE_IN_CHROME_LABELS , CLAUDE_IN_CHROME_DISPLAY_LABELS } from ' ../constants'
1312import MediaThumbnailGroup from ' ./MediaThumbnailGroup.vue'
1413import AppTooltip from ' ./AppTooltip.vue'
1514import FilePickerPopup from ' ./FilePickerPopup.vue'
1615import SlashCommandPickerPopup from ' ./SlashCommandPickerPopup.vue'
16+ import PromptHistoryPickerPopup from ' ./PromptHistoryPickerPopup.vue'
1717
1818// Track visual viewport height for mobile keyboard handling
1919useVisualViewport ()
@@ -48,60 +48,8 @@ const messageText = ref('')
4848const textareaRef = ref (null )
4949const fileInputRef = ref (null )
5050
51- // ── Prompt history navigation (PageUp / PageDown) ────────────────────────
52- // Index into past user messages: -1 = not navigating, 0 = most recent, etc.
53- const historyIndex = ref (- 1 )
54- // Text saved before entering history navigation, restored when exiting
55- let savedDraft = ' '
56-
57- /**
58- * Get the list of past user message texts for the current session, most recent first.
59- */
60- function getHistoryTexts () {
61- const items = store .getSessionItems (props .sessionId )
62- const texts = []
63- for (let i = items .length - 1 ; i >= 0 ; i-- ) {
64- if (items[i].kind !== ' user_message' ) continue
65- const parsed = getParsedContent (items[i])
66- const content = parsed? .message ? .content
67- if (! content) continue
68- // content can be a plain string or an array of blocks
69- if (typeof content === ' string' ) {
70- if (content) texts .push (content)
71- } else if (Array .isArray (content)) {
72- const textBlock = content .find (b => b .type === ' text' )
73- if (textBlock? .text ) texts .push (textBlock .text )
74- }
75- }
76- return texts
77- }
78-
79- /**
80- * Navigate prompt history. delta: -1 = older (PageUp), +1 = newer (PageDown).
81- */
82- function navigateHistory (delta ) {
83- const history = getHistoryTexts ()
84- if (! history .length ) return
85-
86- const newIndex = historyIndex .value + delta
87-
88- if (newIndex < - 1 ) return // Can't go newer than draft
89- if (newIndex >= history .length ) return // Can't go older than oldest
90-
91- if (historyIndex .value === - 1 ) {
92- // Entering history mode: save current text
93- savedDraft = messageText .value
94- }
95-
96- historyIndex .value = newIndex
97-
98- if (newIndex === - 1 ) {
99- // Back to draft
100- messageText .value = savedDraft
101- } else {
102- messageText .value = history[newIndex]
103- }
104- }
51+ // ── Prompt history picker ─────────────────────────────────────────────────
52+ const historyPickerRef = ref (null )
10553const attachButtonId = useId ()
10654const settingsButtonId = useId ()
10755const textareaAnchorId = useId ()
@@ -250,7 +198,7 @@ const placeholderText = computed(() => {
250198 let text = ' Type your message... Use / for commands, @ for file paths'
251199 if (! settingsStore .isTouchDevice ) {
252200 const keys = settingsStore .isMac ? ' ⌘↵ or Ctrl↵' : ' Ctrl↵ or Meta↵'
253- text += ` , ${ keys} to send`
201+ text += ` , Alt+PgUp for history, ${ keys} to send`
254202 }
255203 return text
256204})
@@ -493,6 +441,7 @@ watch(
493441
494442// Restore draft message when session changes
495443watch (() => props .sessionId , async (newId ) => {
444+
496445 const draft = store .getDraftMessage (newId)
497446 messageText .value = draft? .message || ' '
498447 // Adjust textarea height after the DOM updates with restored content
@@ -604,7 +553,6 @@ function onInput(event) {
604553 }
605554
606555 messageText .value = newText
607- historyIndex .value = - 1 // Exit history navigation on new typing
608556 adjustTextareaHeight ()
609557 // Notify server that user is actively preparing a message (debounced)
610558 // This prevents auto-stop of the process due to inactivity timeout
@@ -685,7 +633,7 @@ function onSlashCommandPickerClose() {
685633
686634/**
687635 * Handle keyboard shortcuts in textarea.
688- * Cmd/Ctrl+Enter submits, PageUp/PageDown navigates prompt history.
636+ * Cmd/Ctrl+Enter submits, Alt+ PageUp opens prompt history picker .
689637 */
690638function onKeydown (event ) {
691639 if ((event .metaKey || event .ctrlKey ) && event .key === ' Enter' ) {
@@ -694,19 +642,71 @@ function onKeydown(event) {
694642 return
695643 }
696644
697- // PageUp: older prompt, PageDown: newer prompt
698- if (event .key === ' PageUp' ) {
645+ // Alt+ PageUp: open prompt history picker (or toggle to search if already open)
646+ if (event .altKey && event . key === ' PageUp' ) {
699647 event .preventDefault ()
700- navigateHistory ( 1 ) // 1 = older (higher index )
648+ historyPickerRef . value ? . open ( ) // open() handles already-open case (focuses search )
701649 return
702650 }
703- if (event .key === ' PageDown' ) {
651+
652+ // Alt+PageDown: focus list in prompt history picker (if open)
653+ if (event .altKey && event .key === ' PageDown' ) {
704654 event .preventDefault ()
705- navigateHistory ( - 1 ) // -1 = newer (lower index )
655+ historyPickerRef . value ? . focusList ( )
706656 return
707657 }
708658}
709659
660+ /**
661+ * Handle prompt history replace: overwrite textarea content with selected message.
662+ */
663+ async function onHistoryReplace (text ) {
664+ messageText .value = text
665+ if (textareaRef .value ) {
666+ textareaRef .value .value = text
667+ const inner = textareaRef .value .shadowRoot ? .querySelector (' textarea' )
668+ if (inner) {
669+ inner .value = text
670+ const pos = text .length
671+ inner .setSelectionRange (pos, pos)
672+ }
673+ }
674+ await nextTick ()
675+ textareaRef .value ? .focus ()
676+ adjustTextareaHeight ()
677+ }
678+
679+ /**
680+ * Handle prompt history insert: insert selected message at cursor position.
681+ */
682+ async function onHistoryInsert (text ) {
683+ const inner = textareaRef .value ? .shadowRoot ? .querySelector (' textarea' )
684+ const pos = inner? .selectionStart ?? messageText .value .length
685+ const before = messageText .value .slice (0 , pos)
686+ const after = messageText .value .slice (pos)
687+ const newText = before + text + after
688+ const newPos = pos + text .length
689+
690+ messageText .value = newText
691+ if (textareaRef .value ) {
692+ textareaRef .value .value = newText
693+ if (inner) {
694+ inner .value = newText
695+ inner .setSelectionRange (newPos, newPos)
696+ }
697+ }
698+ await nextTick ()
699+ textareaRef .value ? .focus ()
700+ adjustTextareaHeight ()
701+ }
702+
703+ /**
704+ * Handle prompt history picker close: return focus to textarea.
705+ */
706+ function onHistoryPickerClose () {
707+ textareaRef .value ? .focus ()
708+ }
709+
710710/**
711711 * Handle paste event to capture images from clipboard.
712712 * Only processes image files from clipboard.
@@ -890,6 +890,7 @@ async function handleSend() {
890890
891891 // Clear draft message from store (and IndexedDB)
892892 store .clearDraftMessage (props .sessionId )
893+
893894
894895 // Clear attachments from store and IndexedDB
895896 if (attachmentCount .value > 0 ) {
@@ -943,6 +944,7 @@ function handleCancel() {
943944 * restore dropdowns to their active (server-side) values.
944945 */
945946async function handleReset () {
947+
946948 // Clear text if any
947949 if (messageText .value ) {
948950 messageText .value = ' '
@@ -1010,6 +1012,16 @@ async function handleReset() {
10101012 @close= " onSlashCommandPickerClose"
10111013 / >
10121014
1015+ <!-- Prompt history picker popup triggered by Alt+ PageUp -->
1016+ < PromptHistoryPickerPopup
1017+ ref= " historyPickerRef"
1018+ : session- id= " sessionId"
1019+ : anchor- id= " textareaAnchorId"
1020+ @replace= " onHistoryReplace"
1021+ @insert= " onHistoryInsert"
1022+ @close= " onHistoryPickerClose"
1023+ / >
1024+
10131025 < div class = " message-input-toolbar" >
10141026 <!-- Attachments row: button on left, thumbnails on right -->
10151027 < div class = " message-input-attachments" >
0 commit comments