mirror of
https://github.com/aljazceru/goose.git
synced 2026-02-23 15:34:27 +01:00
feat: use Ctrl/Cmd + ↑/↓ to navigate message history (#1501)
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React, { useEffect, useRef, useState, useMemo } from 'react';
|
||||
import { getApiUrl } from '../config';
|
||||
import { generateSessionId } from '../sessions';
|
||||
import BottomMenu from './BottomMenu';
|
||||
@@ -226,6 +226,20 @@ export default function ChatView({
|
||||
return true;
|
||||
};
|
||||
|
||||
const commandHistory = useMemo(() => {
|
||||
return filteredMessages
|
||||
.reduce<string[]>((history, message) => {
|
||||
if (isUserMessage(message)) {
|
||||
const text = message.content.find((c) => c.type === 'text')?.text?.trim();
|
||||
if (text) {
|
||||
history.push(text);
|
||||
}
|
||||
}
|
||||
return history;
|
||||
}, [])
|
||||
.reverse();
|
||||
}, [filteredMessages, isUserMessage]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full h-screen items-center justify-center">
|
||||
<div className="relative flex items-center h-[36px] w-full bg-bgSubtle border-b border-borderSubtle">
|
||||
@@ -278,7 +292,12 @@ export default function ChatView({
|
||||
|
||||
<div className="relative">
|
||||
{isLoading && <LoadingGoose />}
|
||||
<Input handleSubmit={handleSubmit} isLoading={isLoading} onStop={onStopGoose} />
|
||||
<Input
|
||||
handleSubmit={handleSubmit}
|
||||
isLoading={isLoading}
|
||||
onStop={onStopGoose}
|
||||
commandHistory={commandHistory}
|
||||
/>
|
||||
<BottomMenu hasMessages={hasMessages} setView={setView} />
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -7,12 +7,20 @@ interface InputProps {
|
||||
handleSubmit: (e: React.FormEvent) => void;
|
||||
isLoading?: boolean;
|
||||
onStop?: () => void;
|
||||
commandHistory?: string[];
|
||||
}
|
||||
|
||||
export default function Input({ handleSubmit, isLoading = false, onStop }: InputProps) {
|
||||
export default function Input({
|
||||
handleSubmit,
|
||||
isLoading = false,
|
||||
onStop,
|
||||
commandHistory = [],
|
||||
}: InputProps) {
|
||||
const [value, setValue] = useState('');
|
||||
// State to track if the IME is composing (i.e., in the middle of Japanese IME input)
|
||||
const [isComposing, setIsComposing] = useState(false);
|
||||
const [historyIndex, setHistoryIndex] = useState(-1);
|
||||
const [savedInput, setSavedInput] = useState('');
|
||||
const textAreaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -50,7 +58,45 @@ export default function Input({ handleSubmit, isLoading = false, onStop }: Input
|
||||
setIsComposing(false);
|
||||
};
|
||||
|
||||
const handleHistoryNavigation = (evt: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
evt.preventDefault();
|
||||
|
||||
// Save current input if we're just starting to navigate history
|
||||
if (historyIndex === -1) {
|
||||
setSavedInput(value);
|
||||
}
|
||||
|
||||
// Calculate new history index
|
||||
let newIndex = historyIndex;
|
||||
if (evt.key === 'ArrowUp') {
|
||||
// Move backwards through history
|
||||
if (historyIndex < commandHistory.length - 1) {
|
||||
newIndex = historyIndex + 1;
|
||||
}
|
||||
} else {
|
||||
// Move forwards through history
|
||||
if (historyIndex > -1) {
|
||||
newIndex = historyIndex - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Update index and value
|
||||
setHistoryIndex(newIndex);
|
||||
if (newIndex === -1) {
|
||||
// Restore saved input when going past the end of history
|
||||
setValue(savedInput);
|
||||
} else {
|
||||
setValue(commandHistory[newIndex] || '');
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (evt: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
// Handle command history navigation
|
||||
if ((evt.metaKey || evt.ctrlKey) && (evt.key === 'ArrowUp' || evt.key === 'ArrowDown')) {
|
||||
handleHistoryNavigation(evt);
|
||||
return;
|
||||
}
|
||||
|
||||
if (evt.key === 'Enter') {
|
||||
// should not trigger submit on Enter if it's composing (IME input in progress) or shift is pressed
|
||||
if (evt.shiftKey || isComposing) {
|
||||
@@ -66,6 +112,8 @@ export default function Input({ handleSubmit, isLoading = false, onStop }: Input
|
||||
if (!isLoading && value.trim()) {
|
||||
handleSubmit(new CustomEvent('submit', { detail: { value } }));
|
||||
setValue('');
|
||||
setHistoryIndex(-1);
|
||||
setSavedInput('');
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -75,6 +123,8 @@ export default function Input({ handleSubmit, isLoading = false, onStop }: Input
|
||||
if (value.trim() && !isLoading) {
|
||||
handleSubmit(new CustomEvent('submit', { detail: { value } }));
|
||||
setValue('');
|
||||
setHistoryIndex(-1);
|
||||
setSavedInput('');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -94,7 +144,7 @@ export default function Input({ handleSubmit, isLoading = false, onStop }: Input
|
||||
<textarea
|
||||
autoFocus
|
||||
id="dynamic-textarea"
|
||||
placeholder="What can goose help with?"
|
||||
placeholder="What can goose help with? (Cmd/Ctrl + ↑/↓ for history)"
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
onCompositionStart={handleCompositionStart}
|
||||
|
||||
Reference in New Issue
Block a user