diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index cf09dd8a38..82a5bd73c2 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -816,6 +816,9 @@ export default function ChatView(props: ChatViewProps) { const attachmentPreviewHandoffTimeoutByMessageIdRef = useRef>({}); const sendInFlightRef = useRef(false); const dragDepthRef = useRef(0); + const inputHistoryRef = useRef([]); + const historyIdxRef = useRef(-1); + const historyDraftRef = useRef(''); const terminalOpenByThreadRef = useRef>({}); const setMessagesScrollContainerRef = useCallback((element: HTMLDivElement | null) => { messagesScrollRef.current = element; @@ -2509,6 +2512,14 @@ export default function ChatView(props: ChatViewProps) { composerTerminalContextsRef.current = composerTerminalContexts; }, [composerTerminalContexts]); + useEffect(() => { + inputHistoryRef.current = (activeThread?.messages ?? []) + .filter((m) => m.role === 'user' && m.text.trim()) + .map((m) => m.text.trim()) + .reverse(); + historyIdxRef.current = -1; + }, [activeThread?.id]); // eslint-disable-line react-hooks/exhaustive-deps + useEffect(() => { if (!activeThread?.id) return; if (activeThread.messages.length === 0) { @@ -3178,6 +3189,7 @@ export default function ChatView(props: ChatViewProps) { description: toastCopy.description, }); } + if (promptForSend.trim()) { inputHistoryRef.current.unshift(promptForSend.trim()); historyIdxRef.current = -1; } promptRef.current = ""; clearComposerDraftContent(scopeThreadRef(activeThread.environmentId, threadIdForSend)); setComposerHighlightedItemId(null); @@ -4066,6 +4078,19 @@ export default function ChatView(props: ChatViewProps) { } } + const cur = composerEditorRef.current?.readSnapshot()?.cursor ?? 0; + if (key === 'ArrowUp' && inputHistoryRef.current.length > 0 && !promptRef.current.slice(0, cur).includes('\n')) { + if (historyIdxRef.current === -1) historyDraftRef.current = promptRef.current; + historyIdxRef.current = Math.min(historyIdxRef.current + 1, inputHistoryRef.current.length - 1); + setPrompt(inputHistoryRef.current[historyIdxRef.current]!); + return true; + } + if (key === 'ArrowDown' && historyIdxRef.current >= 0 && !promptRef.current.slice(cur).includes('\n')) { + const next = historyIdxRef.current - 1; + historyIdxRef.current = next; + setPrompt(next >= 0 ? inputHistoryRef.current[next]! : historyDraftRef.current); + return true; + } if (key === "Enter" && !event.shiftKey) { void onSend(); return true;