diff --git a/src/app/chat/components/chat-input-bar.tsx b/src/app/chat/components/chat-input-bar.tsx index 1e92038b..2bc32532 100644 --- a/src/app/chat/components/chat-input-bar.tsx +++ b/src/app/chat/components/chat-input-bar.tsx @@ -10,6 +10,7 @@ import { ChatSendButton } from "./chat-send-button"; import styles from "./chat-input-bar.module.css"; const log = new Logger("AppChatComponentsChatInputBar"); +const POINTER_SEND_CLICK_DEDUPE_MS = 500; export interface ChatInputBarProps { disabled?: boolean; @@ -19,12 +20,15 @@ export function ChatInputBar({ disabled = false }: ChatInputBarProps) { const dispatch = useChatDispatch(); const [input, setInput] = useState(""); const [isFocused, setIsFocused] = useState(false); + const barRef = useRef(null); const textareaRef = useRef(null); + const lastPointerSendAtRef = useRef(0); const hasContent = input.trim().length > 0; useKeyboardHeight({ targetRef: textareaRef, + containerRef: barRef, active: isFocused, onKeyboardDismiss: () => { log.important("info", "[chat-input-bar] keyboard dismissed", { @@ -51,9 +55,23 @@ export function ChatInputBar({ disabled = false }: ChatInputBarProps) { setIsFocused(focused); }; - const handleSend = () => { - if (!hasContent) return; + const handleSend = (source: "click" | "keyboard" | "pointerdown") => { + if (source === "click") { + const elapsed = Date.now() - lastPointerSendAtRef.current; + if (elapsed >= 0 && elapsed < POINTER_SEND_CLICK_DEDUPE_MS) { + log.important("info", "[chat-input-bar] suppress duplicate click", { + elapsed, + contentLength: input.length, + hasContent, + disabled, + isFocused, + }); + return; + } + } + if (disabled || !hasContent) return; log.debug("[chat-input-bar] handleSend", { + source, contentLength: input.length, contentPreview: input.slice(0, 50), hasContent, @@ -61,6 +79,7 @@ export function ChatInputBar({ disabled = false }: ChatInputBarProps) { isFocused, }); log.important("info", "[chat-input-bar] send", { + source, contentLength: input.length, hasContent, disabled, @@ -71,21 +90,30 @@ export function ChatInputBar({ disabled = false }: ChatInputBarProps) { textareaRef.current?.focus(); }; + const handlePointerDownSend = () => { + lastPointerSendAtRef.current = Date.now(); + handleSend("pointerdown"); + }; + return ( -
+
handleSend("keyboard")} onFocusChange={handleFocusChange} disabled={disabled} /> handleSend("click")} + onPointerDownSend={handlePointerDownSend} />
diff --git a/src/app/chat/components/chat-send-button.tsx b/src/app/chat/components/chat-send-button.tsx index f0bc9346..cb08c8aa 100644 --- a/src/app/chat/components/chat-send-button.tsx +++ b/src/app/chat/components/chat-send-button.tsx @@ -18,12 +18,14 @@ export interface ChatSendButtonProps { disabled: boolean; hasContent: boolean; onClick: () => void; + onPointerDownSend: () => void; } export function ChatSendButton({ disabled, hasContent, onClick, + onPointerDownSend, }: ChatSendButtonProps) { return (