diff --git a/ui/desktop/src/components/ChatView.tsx b/ui/desktop/src/components/ChatView.tsx index f3ebd353..52676856 100644 --- a/ui/desktop/src/components/ChatView.tsx +++ b/ui/desktop/src/components/ChatView.tsx @@ -193,12 +193,7 @@ export default function ChatView({ setView }: { setView: (view: View) => void })
{isLoading && } - +
diff --git a/ui/desktop/src/components/Input.tsx b/ui/desktop/src/components/Input.tsx index f90b778e..b330d163 100644 --- a/ui/desktop/src/components/Input.tsx +++ b/ui/desktop/src/components/Input.tsx @@ -5,27 +5,21 @@ import { Attach, Send } from './icons'; interface InputProps { handleSubmit: (e: React.FormEvent) => void; - disabled?: boolean; isLoading?: boolean; onStop?: () => void; } -export default function Input({ - handleSubmit, - disabled = false, - isLoading = false, - onStop, -}: InputProps) { +export default function Input({ handleSubmit, isLoading = false, onStop }: 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 textAreaRef = useRef(null); useEffect(() => { - if (textAreaRef.current && !disabled) { + if (textAreaRef.current) { textAreaRef.current.focus(); } - }, [disabled, value]); + }, [value]); const useAutosizeTextArea = (textAreaRef: HTMLTextAreaElement | null, value: string) => { useEffect(() => { @@ -57,10 +51,19 @@ export default function Input({ }; const handleKeyDown = (evt: React.KeyboardEvent) => { - // Only trigger submit on Enter if not composing (IME input in progress) and shift is not pressed - if (evt.key === 'Enter' && !evt.shiftKey && !isComposing) { + 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) { + // Allow line break for Shift+Enter or during IME composition + return; + } + + // Prevent default Enter behavior when loading or when not loading but has content + // So it won't trigger a new line evt.preventDefault(); - if (value.trim()) { + + // Only submit if not loading and has content + if (!isLoading && value.trim()) { handleSubmit(new CustomEvent('submit', { detail: { value } })); setValue(''); } @@ -69,7 +72,7 @@ export default function Input({ const onFormSubmit = (e: React.FormEvent) => { e.preventDefault(); - if (value.trim()) { + if (value.trim() && !isLoading) { handleSubmit(new CustomEvent('submit', { detail: { value } })); setValue(''); } @@ -97,7 +100,6 @@ export default function Input({ onCompositionStart={handleCompositionStart} onCompositionEnd={handleCompositionEnd} onKeyDown={handleKeyDown} - disabled={disabled} ref={textAreaRef} rows={1} style={{ @@ -105,19 +107,14 @@ export default function Input({ maxHeight: `${maxHeight}px`, overflowY: 'auto', }} - className={`w-full outline-none border-none focus:ring-0 bg-transparent p-0 text-base resize-none text-textStandard ${ - disabled ? 'cursor-not-allowed opacity-50' : '' - }`} + className="w-full outline-none border-none focus:ring-0 bg-transparent p-0 text-base resize-none text-textStandard" /> @@ -126,7 +123,11 @@ export default function Input({ type="button" size="icon" variant="ghost" - onClick={onStop} + onClick={(e) => { + e.preventDefault(); + e.stopPropagation(); + onStop(); + }} className="absolute right-2 top-1/2 -translate-y-1/2 [&_svg]:size-5 text-textSubtle hover:text-textStandard" > @@ -136,9 +137,9 @@ export default function Input({ type="submit" size="icon" variant="ghost" - disabled={disabled || !value.trim()} + disabled={!value.trim()} className={`absolute right-2 top-1/2 -translate-y-1/2 text-textSubtle hover:text-textStandard ${ - disabled || !value.trim() ? 'text-textSubtle cursor-not-allowed' : '' + !value.trim() ? 'text-textSubtle cursor-not-allowed' : '' }`} >