Add keyboard shortcuts system with reusable hint components
- Implement centralized keyboard registry with 20+ shortcuts - Add instance navigation (Cmd+[1-9], Cmd+[/]) - Add session navigation (Cmd+Shift+[1-9], Cmd+Shift+[/]) - Add agent/model cycling (Tab, Cmd+Shift+M) - Add input shortcuts (Cmd+P focus, Cmd+K clear, ↑↓ history) - Add command palette (Cmd+Shift+P) with 8 MVP commands - Implement message history per folder in IndexedDB (max 100) - Create reusable Kbd and HintRow components - Replace all keyboard hint rendering with consistent components - Use text-based shortcuts (Cmd+Shift+M) for clarity
This commit is contained in:
@@ -1,9 +1,14 @@
|
||||
import { createSignal, Show } from "solid-js"
|
||||
import { createSignal, Show, onMount, createEffect } from "solid-js"
|
||||
import AgentSelector from "./agent-selector"
|
||||
import ModelSelector from "./model-selector"
|
||||
import { addToHistory, getHistory } from "../stores/message-history"
|
||||
import Kbd from "./kbd"
|
||||
import HintRow from "./hint-row"
|
||||
import { isMac } from "../lib/keyboard-utils"
|
||||
|
||||
interface PromptInputProps {
|
||||
instanceId: string
|
||||
instanceFolder: string
|
||||
sessionId: string
|
||||
onSend: (prompt: string) => Promise<void>
|
||||
disabled?: boolean
|
||||
@@ -16,12 +21,56 @@ interface PromptInputProps {
|
||||
export default function PromptInput(props: PromptInputProps) {
|
||||
const [prompt, setPrompt] = createSignal("")
|
||||
const [sending, setSending] = createSignal(false)
|
||||
const [history, setHistory] = createSignal<string[]>([])
|
||||
const [historyIndex, setHistoryIndex] = createSignal(-1)
|
||||
const [isFocused, setIsFocused] = createSignal(false)
|
||||
let textareaRef: HTMLTextAreaElement | undefined
|
||||
|
||||
onMount(async () => {
|
||||
const loaded = await getHistory(props.instanceFolder)
|
||||
setHistory(loaded)
|
||||
})
|
||||
|
||||
function handleKeyDown(e: KeyboardEvent) {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault()
|
||||
handleSend()
|
||||
return
|
||||
}
|
||||
|
||||
const textarea = textareaRef
|
||||
if (!textarea) return
|
||||
|
||||
const atStart = textarea.selectionStart === 0 && textarea.selectionEnd === 0
|
||||
const currentHistory = history()
|
||||
|
||||
if (e.key === "ArrowUp" && atStart && currentHistory.length > 0) {
|
||||
e.preventDefault()
|
||||
const newIndex = historyIndex() === -1 ? 0 : Math.min(historyIndex() + 1, currentHistory.length - 1)
|
||||
setHistoryIndex(newIndex)
|
||||
setPrompt(currentHistory[newIndex])
|
||||
setTimeout(() => {
|
||||
textarea.style.height = "auto"
|
||||
textarea.style.height = Math.min(textarea.scrollHeight, 200) + "px"
|
||||
}, 0)
|
||||
return
|
||||
}
|
||||
|
||||
if (e.key === "ArrowDown" && historyIndex() >= 0) {
|
||||
e.preventDefault()
|
||||
const newIndex = historyIndex() - 1
|
||||
if (newIndex >= 0) {
|
||||
setHistoryIndex(newIndex)
|
||||
setPrompt(currentHistory[newIndex])
|
||||
} else {
|
||||
setHistoryIndex(-1)
|
||||
setPrompt("")
|
||||
}
|
||||
setTimeout(() => {
|
||||
textarea.style.height = "auto"
|
||||
textarea.style.height = Math.min(textarea.scrollHeight, 200) + "px"
|
||||
}, 0)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +80,12 @@ export default function PromptInput(props: PromptInputProps) {
|
||||
|
||||
setSending(true)
|
||||
try {
|
||||
await addToHistory(props.instanceFolder, text)
|
||||
|
||||
const updated = await getHistory(props.instanceFolder)
|
||||
setHistory(updated)
|
||||
setHistoryIndex(-1)
|
||||
|
||||
await props.onSend(text)
|
||||
setPrompt("")
|
||||
|
||||
@@ -49,6 +104,7 @@ export default function PromptInput(props: PromptInputProps) {
|
||||
function handleInput(e: Event) {
|
||||
const target = e.target as HTMLTextAreaElement
|
||||
setPrompt(target.value)
|
||||
setHistoryIndex(-1)
|
||||
|
||||
target.style.height = "auto"
|
||||
target.style.height = Math.min(target.scrollHeight, 200) + "px"
|
||||
@@ -66,6 +122,8 @@ export default function PromptInput(props: PromptInputProps) {
|
||||
value={prompt()}
|
||||
onInput={handleInput}
|
||||
onKeyDown={handleKeyDown}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={() => setIsFocused(false)}
|
||||
disabled={sending() || props.disabled}
|
||||
rows={1}
|
||||
/>
|
||||
@@ -76,9 +134,10 @@ export default function PromptInput(props: PromptInputProps) {
|
||||
</button>
|
||||
</div>
|
||||
<div class="prompt-input-hints">
|
||||
<span class="hint">
|
||||
<kbd>Enter</kbd> to send, <kbd>Shift+Enter</kbd> for new line
|
||||
</span>
|
||||
<HintRow>
|
||||
<Kbd>Enter</Kbd> to send • <Kbd>Shift+Enter</Kbd> for new line • <Kbd>↑↓</Kbd> for history •{" "}
|
||||
<Kbd shortcut="cmd+p" /> to focus
|
||||
</HintRow>
|
||||
<div class="flex items-center gap-2">
|
||||
<AgentSelector
|
||||
instanceId={props.instanceId}
|
||||
|
||||
Reference in New Issue
Block a user