diff --git a/src/app/components/ChatInterface/ChatInterface.module.scss b/src/app/components/ChatInterface/ChatInterface.module.scss index e3b963dd..9f4f55bf 100644 --- a/src/app/components/ChatInterface/ChatInterface.module.scss +++ b/src/app/components/ChatInterface/ChatInterface.module.scss @@ -169,21 +169,33 @@ border-top: 1px solid var(--color-border); background-color: var(--color-background); flex-shrink: 0; + align-items: flex-end; } .input { flex: 1; - padding-left: 10px; + padding: 10px; + min-height: 40px; + max-height: 200px; + line-height: 1.5; + overflow-y: auto; + + &::-webkit-scrollbar { + width: 0; + display: none; + } } .sendButton { padding: $spacing-sm $spacing-md; + margin-bottom: 2px; } .stopButton { padding: $spacing-sm $spacing-md; background-color: var(--color-error); color: white; + margin-bottom: 2px; &:hover { opacity: 0.9; diff --git a/src/app/components/ChatInterface/ChatInterface.tsx b/src/app/components/ChatInterface/ChatInterface.tsx index 7abe280a..93c0d80b 100644 --- a/src/app/components/ChatInterface/ChatInterface.tsx +++ b/src/app/components/ChatInterface/ChatInterface.tsx @@ -9,8 +9,8 @@ import React, { FormEvent, } from "react"; import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Send, Bot, LoaderCircle, SquarePen, History, X } from "lucide-react"; +import { Textarea } from "@/components/ui/textarea"; +import { Send, Bot, LoaderCircle, SquarePen, History } from "lucide-react"; import { ChatMessage } from "../ChatMessage/ChatMessage"; import { ThreadHistorySidebar } from "../ThreadHistorySidebar/ThreadHistorySidebar"; import type { SubAgent, TodoItem, ToolCall } from "../../types/types"; @@ -45,7 +45,9 @@ export const ChatInterface = React.memo( }) => { const [input, setInput] = useState(""); const [isThreadHistoryOpen, setIsThreadHistoryOpen] = useState(false); + const [isComposing, setIsComposing] = useState(false); const messagesEndRef = useRef(null); + const textareaRef = useRef(null); const { messages, isLoading, sendMessage, stopStream } = useChat( threadId, @@ -58,17 +60,48 @@ export const ChatInterface = React.memo( messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages]); + // Auto-resize textarea + useEffect(() => { + if (textareaRef.current) { + textareaRef.current.style.height = "auto"; + textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, 200)}px`; + } + }, [input]); + const handleSubmit = useCallback( - (e: FormEvent) => { - e.preventDefault(); + (e?: FormEvent) => { + e?.preventDefault(); const messageText = input.trim(); if (!messageText || isLoading) return; sendMessage(messageText); setInput(""); + // Reset textarea height after sending + if (textareaRef.current) { + textareaRef.current.style.height = "auto"; + } }, [input, isLoading, sendMessage], ); + const handleKeyDown = useCallback( + (e: React.KeyboardEvent) => { + // Don't submit if IME is composing (Japanese/Chinese/Korean input) + if (e.key === "Enter" && !e.shiftKey && !isComposing) { + e.preventDefault(); + handleSubmit(); + } + }, + [handleSubmit, isComposing], + ); + + const handleCompositionStart = useCallback(() => { + setIsComposing(true); + }, []); + + const handleCompositionEnd = useCallback(() => { + setIsComposing(false); + }, []); + const handleNewThread = useCallback(() => { // Cancel any ongoing thread when creating new thread if (isLoading) { @@ -243,12 +276,17 @@ export const ChatInterface = React.memo(
- setInput(e.target.value)} - placeholder="Type your message..." + onKeyDown={handleKeyDown} + onCompositionStart={handleCompositionStart} + onCompositionEnd={handleCompositionEnd} + placeholder="Type your message... (Shift+Enter for new line)" disabled={isLoading} className={styles.input} + rows={1} /> {isLoading ? (