diff --git a/dashboard/components/SessionCard.js b/dashboard/components/SessionCard.js index a6ea69d..cd7551d 100644 --- a/dashboard/components/SessionCard.js +++ b/dashboard/components/SessionCard.js @@ -167,6 +167,7 @@ export function SessionCard({ session, onClick, conversation, onFetchConversatio status=${session.status} onRespond=${onRespond} autocompleteConfig=${autocompleteConfig} + conversation=${conversation} /> `} diff --git a/dashboard/components/SimpleInput.js b/dashboard/components/SimpleInput.js index f833d04..079d552 100644 --- a/dashboard/components/SimpleInput.js +++ b/dashboard/components/SimpleInput.js @@ -2,7 +2,7 @@ import { html, useState, useRef, useCallback, useMemo, useEffect } from '../lib/ import { getStatusMeta } from '../utils/status.js'; import { getTriggerInfo as _getTriggerInfo, filteredSkills as _filteredSkills } from '../utils/autocomplete.js'; -export function SimpleInput({ sessionId, status, onRespond, autocompleteConfig = null }) { +export function SimpleInput({ sessionId, status, onRespond, autocompleteConfig = null, conversation }) { const [text, setText] = useState(''); const [focused, setFocused] = useState(false); const [sending, setSending] = useState(false); @@ -12,8 +12,15 @@ export function SimpleInput({ sessionId, status, onRespond, autocompleteConfig = const [selectedIndex, setSelectedIndex] = useState(0); const textareaRef = useRef(null); const autocompleteRef = useRef(null); + const historyIndexRef = useRef(-1); + const draftRef = useRef(''); const meta = getStatusMeta(status); + const userHistory = useMemo( + () => (conversation || []).filter(m => m.role === 'user').map(m => m.content), + [conversation] + ); + const getTriggerInfo = useCallback((value, cursorPos) => { return _getTriggerInfo(value, cursorPos, autocompleteConfig); }, [autocompleteConfig]); @@ -100,6 +107,7 @@ export function SimpleInput({ sessionId, status, onRespond, autocompleteConfig = try { await onRespond(sessionId, text.trim(), true, 0); setText(''); + historyIndexRef.current = -1; } catch (err) { setError('Failed to send message'); console.error('SimpleInput send error:', err); @@ -130,6 +138,7 @@ export function SimpleInput({ sessionId, status, onRespond, autocompleteConfig = const value = e.target.value; const cursorPos = e.target.selectionStart; setText(value); + historyIndexRef.current = -1; setTriggerInfo(getTriggerInfo(value, cursorPos)); e.target.style.height = 'auto'; e.target.style.height = e.target.scrollHeight + 'px'; @@ -169,6 +178,57 @@ export function SimpleInput({ sessionId, status, onRespond, autocompleteConfig = } } + // History navigation (only when autocomplete is closed) + if (e.key === 'ArrowUp' && !showAutocomplete && + e.target.selectionStart === 0 && e.target.selectionEnd === 0 && + userHistory.length > 0) { + e.preventDefault(); + if (historyIndexRef.current === -1) { + draftRef.current = text; + historyIndexRef.current = userHistory.length - 1; + } else if (historyIndexRef.current > 0) { + historyIndexRef.current -= 1; + } + // Clamp if history shrank since last navigation + if (historyIndexRef.current >= userHistory.length) { + historyIndexRef.current = userHistory.length - 1; + } + const historyText = userHistory[historyIndexRef.current]; + setText(historyText); + setTimeout(() => { + if (textareaRef.current) { + textareaRef.current.selectionStart = historyText.length; + textareaRef.current.selectionEnd = historyText.length; + textareaRef.current.style.height = 'auto'; + textareaRef.current.style.height = textareaRef.current.scrollHeight + 'px'; + } + }, 0); + return; + } + + if (e.key === 'ArrowDown' && !showAutocomplete && + historyIndexRef.current !== -1) { + e.preventDefault(); + historyIndexRef.current += 1; + let newText; + if (historyIndexRef.current >= userHistory.length) { + historyIndexRef.current = -1; + newText = draftRef.current; + } else { + newText = userHistory[historyIndexRef.current]; + } + setText(newText); + setTimeout(() => { + if (textareaRef.current) { + textareaRef.current.selectionStart = newText.length; + textareaRef.current.selectionEnd = newText.length; + textareaRef.current.style.height = 'auto'; + textareaRef.current.style.height = textareaRef.current.scrollHeight + 'px'; + } + }, 0); + return; + } + // Normal Enter-to-submit (only when dropdown is closed) if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault();