91 lines
2.6 KiB
TypeScript
91 lines
2.6 KiB
TypeScript
"use client";
|
|
import { useRef, useState } from "react";
|
|
|
|
import { useChatDispatch } from "@/stores/chat/chat-context";
|
|
import { useKeyboardHeight } from "@/hooks";
|
|
import { Logger } from "@/utils";
|
|
|
|
import { ChatInputTextField } from "./chat-input-text-field";
|
|
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;
|
|
}
|
|
|
|
export function ChatInputBar({ disabled = false }: ChatInputBarProps) {
|
|
const dispatch = useChatDispatch();
|
|
const [input, setInput] = useState("");
|
|
const [isFocused, setIsFocused] = useState(false);
|
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
const lastPointerSendAtRef = useRef(0);
|
|
|
|
const hasContent = input.trim().length > 0;
|
|
|
|
useKeyboardHeight({
|
|
targetRef: textareaRef,
|
|
active: isFocused,
|
|
});
|
|
|
|
const handleInputChange = (value: string) => {
|
|
setInput(value);
|
|
};
|
|
|
|
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.debug("[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,
|
|
disabled,
|
|
isFocused,
|
|
});
|
|
dispatch({ type: "ChatSendMessage", content: input });
|
|
setInput("");
|
|
textareaRef.current?.focus();
|
|
};
|
|
|
|
const handlePointerDownSend = () => {
|
|
lastPointerSendAtRef.current = Date.now();
|
|
handleSend("pointerdown");
|
|
};
|
|
|
|
return (
|
|
<div className={`${styles.bar} ${isFocused ? styles.barFocused : ""}`}>
|
|
<div className={`${styles.row} ${isFocused ? styles.rowFocused : ""}`}>
|
|
<ChatInputTextField
|
|
ref={textareaRef}
|
|
value={input}
|
|
onChange={handleInputChange}
|
|
onSubmit={() => handleSend("keyboard")}
|
|
onFocusChange={setIsFocused}
|
|
disabled={disabled}
|
|
/>
|
|
<ChatSendButton
|
|
disabled={disabled}
|
|
hasContent={hasContent}
|
|
onClick={() => handleSend("click")}
|
|
onPointerDownSend={handlePointerDownSend}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|