Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions ui/desktop/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -11,11 +11,11 @@
import { Card } from './ui/card';
import { ScrollArea, ScrollAreaHandle } from './ui/scroll-area';
import UserMessage from './UserMessage';
import { askAi } from '../utils/askAI';

Check warning on line 14 in ui/desktop/src/components/ChatView.tsx

View workflow job for this annotation

GitHub Actions / Lint Electron Desktop App

'askAi' is defined but never used. Allowed unused vars must match /^_/u
import Splash from './Splash';
import 'react-toastify/dist/ReactToastify.css';
import { useMessageStream } from '../hooks/useMessageStream';
import { Message, createUserMessage, getTextContent } from '../types/message';

Check warning on line 18 in ui/desktop/src/components/ChatView.tsx

View workflow job for this annotation

GitHub Actions / Lint Electron Desktop App

'getTextContent' is defined but never used. Allowed unused vars must match /^_/u

export interface ChatType {
id: number;
Expand Down Expand Up @@ -226,6 +226,20 @@
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">
Expand Down Expand Up @@ -278,7 +292,12 @@

<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>
Expand Down
54 changes: 52 additions & 2 deletions ui/desktop/src/components/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,20 @@ interface InputProps {
handleSubmit: (e: React.FormEvent) => void;
isLoading?: boolean;
onStop?: () => void;
commandHistory?: string[];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we want to maintain a fixed size array?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @yingjiehe-xyz , what do you mean a "fixed size array"? In JavaScript the array size is not fixed.
Or are you suggesting the max size of the array for performance consideration? I think that is not necessary here too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry for the confusing, yes, the max size for history, I think so, not too many messages right now

}

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(() => {
Expand Down Expand Up @@ -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) {
Expand All @@ -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('');
}
}
};
Expand All @@ -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('');
}
};

Expand All @@ -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}
Expand Down
Loading