fix(chat): prevent message send during IME composition (#107)
* fix(chat): prevent message send during IME composition Add isComposing check to ChatInput to prevent messages from being sent during Japanese/CJK character conversion. This fixes the issue where pressing Enter to confirm IME composition would immediately send the message instead of just completing the character conversion. Changes: - Add early return in handleKeyDown when IME is composing - Add explicit isComposing check to Enter key handler Fixes issue where Enter during IME composition sends incomplete text. * fix(chat): improve IME composition detection with keyCode 229 fallback Add isIMECompositionEvent helper function that checks both isComposing and keyCode === 229, as recommended by MDN. This fixes IME input issues on WebKit-based browsers (including Tauri WebView on macOS) where compositionend may fire before keydown, causing isComposing to be false when Enter key is processed. Changes: - Add isIMECompositionEvent helper function with dual detection - Update handleKeyDown to use the new helper for IME checks Fixes MacUI desktop app issue where pressing Enter during IME composition would send the message instead of confirming the input. * fix(agent-manager): prevent message send during IME composition Apply the same IME composition fix as ChatInput to AgentManagerEmptyState. Add isIMECompositionEvent helper and check it in handleKeyDown to prevent accidental form submission when confirming IME input with Enter key. * fix(agent-manager): add redundant IME check to Enter key handler Add explicit isIMECompositionEvent check to the Enter key condition in handleKeyDown. Although the early return already handles this, this change ensures the implementation pattern matches ChatInput.tsx exactly for consistency and safety.
This commit is contained in:
@@ -47,6 +47,16 @@ interface ChatInputProps {
|
||||
|
||||
const isPrimaryMode = (mode?: string) => mode === 'primary' || mode === 'all' || mode === undefined || mode === null;
|
||||
|
||||
/**
|
||||
* Detects if a keyboard event is part of IME composition.
|
||||
* Uses both isComposing and keyCode === 229 (MDN recommended).
|
||||
* WebKit may fire compositionend before keydown, causing isComposing to be false
|
||||
* while keyCode remains 229, so both checks are needed.
|
||||
*/
|
||||
const isIMECompositionEvent = (e: React.KeyboardEvent): boolean => {
|
||||
return e.nativeEvent.isComposing || e.nativeEvent.keyCode === 229;
|
||||
};
|
||||
|
||||
export const ChatInput: React.FC<ChatInputProps> = ({ onOpenSettings, scrollToBottom }) => {
|
||||
const [message, setMessage] = React.useState('');
|
||||
const [isDragging, setIsDragging] = React.useState(false);
|
||||
@@ -476,6 +486,9 @@ export const ChatInput: React.FC<ChatInputProps> = ({ onOpenSettings, scrollToBo
|
||||
}, [sessionPhase, queuedMessages.length, currentSessionId, currentProviderId, currentModelId, sessionAbortFlags]);
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
// Early return during IME composition to prevent interference with autocomplete
|
||||
// Uses keyCode === 229 fallback for WebKit where compositionend fires before keydown
|
||||
if (isIMECompositionEvent(e)) return;
|
||||
|
||||
if (showCommandAutocomplete && commandRef.current) {
|
||||
if (e.key === 'Enter' || e.key === 'ArrowUp' || e.key === 'ArrowDown' || e.key === 'Escape' || e.key === 'Tab') {
|
||||
@@ -508,7 +521,7 @@ export const ChatInput: React.FC<ChatInputProps> = ({ onOpenSettings, scrollToBo
|
||||
}
|
||||
|
||||
// Handle Enter/Ctrl+Enter based on queue mode
|
||||
if (e.key === 'Enter' && !e.shiftKey && !isMobile) {
|
||||
if (e.key === 'Enter' && !e.shiftKey && !isMobile && !isIMECompositionEvent(e)) {
|
||||
e.preventDefault();
|
||||
|
||||
const isCtrlEnter = e.ctrlKey || e.metaKey;
|
||||
|
||||
@@ -23,6 +23,16 @@ const MAX_FILE_SIZE = 10 * 1024 * 1024;
|
||||
/** Max number of concurrent runs */
|
||||
const MAX_MODELS = 5;
|
||||
|
||||
/**
|
||||
* Detects if a keyboard event is part of IME composition.
|
||||
* Uses both isComposing and keyCode === 229 (MDN recommended).
|
||||
* WebKit may fire compositionend before keydown, causing isComposing to be false
|
||||
* while keyCode remains 229, so both checks are needed.
|
||||
*/
|
||||
const isIMECompositionEvent = (e: React.KeyboardEvent): boolean => {
|
||||
return e.nativeEvent.isComposing || e.nativeEvent.keyCode === 229;
|
||||
};
|
||||
|
||||
/** Attached file for agent manager */
|
||||
interface AttachedFile {
|
||||
id: string;
|
||||
@@ -177,8 +187,11 @@ export const AgentManagerEmptyState: React.FC<AgentManagerEmptyStateProps> = ({
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
// Early return during IME composition
|
||||
if (isIMECompositionEvent(e)) return;
|
||||
|
||||
// Enter submits if valid, Shift+Enter adds newline
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
if (e.key === 'Enter' && !e.shiftKey && !isIMECompositionEvent(e)) {
|
||||
e.preventDefault();
|
||||
if (isValid && !isSubmittingOrCreating) {
|
||||
handleSubmit(e as unknown as React.FormEvent);
|
||||
|
||||
Reference in New Issue
Block a user