add prompt history rocker
This commit is contained in:
@@ -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"
|
||||
/>
|
||||
<Show when={hasHistory()}>
|
||||
<div class="prompt-history-top">
|
||||
<button
|
||||
type="button"
|
||||
class="prompt-history-button"
|
||||
onClick={() => selectPreviousHistory(true)}
|
||||
disabled={!canHistoryGoPrevious()}
|
||||
aria-label="Previous prompt"
|
||||
>
|
||||
<svg viewBox="0 0 20 20" fill="currentColor" class="h-3 w-3" aria-hidden="true">
|
||||
<path d="M10 6l-4 5h8L10 6z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="prompt-history-bottom">
|
||||
<button
|
||||
type="button"
|
||||
class="prompt-history-button"
|
||||
onClick={() => selectNextHistory(true)}
|
||||
disabled={!canHistoryGoNext()}
|
||||
aria-label="Next prompt"
|
||||
>
|
||||
<svg viewBox="0 0 20 20" fill="currentColor" class="h-3 w-3" aria-hidden="true">
|
||||
<path d="M10 14l4-5H6l4 5z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={shouldShowOverlay()}>
|
||||
<div class={`prompt-input-overlay ${mode() === "shell" ? "shell-mode" : ""}`}>
|
||||
<Show
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
}
|
||||
|
||||
.prompt-input {
|
||||
@apply flex-1 w-full min-h-[56px] max-h-[96px] px-3 pt-2.5 pb-4 border rounded-md text-sm resize-none outline-none transition-colors;
|
||||
@apply flex-1 w-full min-h-[56px] max-h-[96px] pl-3 pr-10 pt-2.5 pb-4 border rounded-md text-sm resize-none outline-none transition-colors;
|
||||
font-family: inherit;
|
||||
background-color: var(--surface-base);
|
||||
color: inherit;
|
||||
@@ -51,6 +51,42 @@
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.prompt-history-top,
|
||||
.prompt-history-bottom {
|
||||
position: absolute;
|
||||
right: 0.35rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.prompt-history-top {
|
||||
top: 0.3rem;
|
||||
}
|
||||
|
||||
.prompt-history-bottom {
|
||||
bottom: 0.3rem;
|
||||
}
|
||||
|
||||
.prompt-history-button {
|
||||
@apply w-8 h-8 flex items-center justify-center rounded-md;
|
||||
color: var(--text-muted);
|
||||
background-color: rgba(15, 23, 42, 0.04);
|
||||
transition: background-color 0.15s ease, color 0.15s ease;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.prompt-history-button:hover:not(:disabled) {
|
||||
background-color: var(--surface-secondary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.prompt-history-button:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.prompt-overlay-text {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
Reference in New Issue
Block a user