diff --git a/packages/ui/src/components/prompt-input.tsx b/packages/ui/src/components/prompt-input.tsx index f15ac22f..351cbbbe 100644 --- a/packages/ui/src/components/prompt-input.tsx +++ b/packages/ui/src/components/prompt-input.tsx @@ -519,31 +519,19 @@ export default function PromptInput(props: PromptInputProps) { return } - const atStart = textarea.selectionStart === 0 && textarea.selectionEnd === 0 - const currentHistory = history() - - if (e.key === "ArrowUp" && !showPicker() && atStart && currentHistory.length > 0) { - e.preventDefault() - if (historyIndex() === -1) { - setHistoryDraft(prompt()) + if (e.key === "ArrowUp") { + const handled = selectPreviousHistory() + if (handled) { + e.preventDefault() + return } - const newIndex = historyIndex() === -1 ? 0 : Math.min(historyIndex() + 1, currentHistory.length - 1) - setHistoryIndex(newIndex) - setPrompt(currentHistory[newIndex]) - return } - if (e.key === "ArrowDown" && !showPicker() && historyIndex() >= 0) { - e.preventDefault() - const newIndex = historyIndex() - 1 - if (newIndex >= 0) { - setHistoryIndex(newIndex) - setPrompt(currentHistory[newIndex]) - } else { - setHistoryIndex(-1) - const draft = historyDraft() - setPrompt(draft ?? "") - setHistoryDraft(null) + if (e.key === "ArrowDown") { + const handled = selectNextHistory() + if (handled) { + e.preventDefault() + return } } } @@ -602,6 +590,60 @@ export default function PromptInput(props: PromptInputProps) { } } + function focusTextareaEnd() { + if (!textareaRef) return + setTimeout(() => { + if (!textareaRef) return + const pos = textareaRef.value.length + textareaRef.setSelectionRange(pos, pos) + textareaRef.focus() + }, 0) + } + + function canUseHistory(force = false) { + if (force) return true + if (showPicker()) return false + const textarea = textareaRef + if (!textarea) return false + return textarea.selectionStart === 0 && textarea.selectionEnd === 0 + } + + function selectPreviousHistory(force = false) { + const entries = history() + if (entries.length === 0) return false + if (!canUseHistory(force)) return false + + if (historyIndex() === -1) { + setHistoryDraft(prompt()) + } + + const newIndex = historyIndex() === -1 ? 0 : Math.min(historyIndex() + 1, entries.length - 1) + setHistoryIndex(newIndex) + setPrompt(entries[newIndex]) + focusTextareaEnd() + return true + } + + function selectNextHistory(force = false) { + const entries = history() + if (entries.length === 0) return false + if (!canUseHistory(force)) return false + if (historyIndex() === -1) return false + + const newIndex = historyIndex() - 1 + if (newIndex >= 0) { + setHistoryIndex(newIndex) + setPrompt(entries[newIndex]) + } else { + setHistoryIndex(-1) + const draft = historyDraft() + setPrompt(draft ?? "") + setHistoryDraft(null) + } + focusTextareaEnd() + return true + } + function handleAbort() { if (!props.onAbortSession || !props.isSessionBusy) return void props.onAbortSession() @@ -828,6 +870,10 @@ export default function PromptInput(props: PromptInputProps) { const canStop = () => Boolean(props.isSessionBusy && props.onAbortSession) + const hasHistory = () => history().length > 0 + const canHistoryGoPrevious = () => hasHistory() && (historyIndex() === -1 || historyIndex() < history().length - 1) + const canHistoryGoNext = () => historyIndex() >= 0 + const canSend = () => { if (props.disabled) return false const hasText = prompt().trim().length > 0 @@ -986,6 +1032,34 @@ export default function PromptInput(props: PromptInputProps) { autoCapitalize="off" autocomplete="off" /> + +
+ +
+
+ +
+