Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/auto-expand-chat-input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"think-app": patch
"think-extension": patch
---

Add auto-expanding chat input that grows as users type
24 changes: 19 additions & 5 deletions app/src/components/ChatInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function ChatInput({
placeholder = "Type your message...",
className,
}: ChatInputProps) {
const inputRef = useRef<HTMLInputElement>(null);
const inputRef = useRef<HTMLTextAreaElement>(null);

const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter" && !e.shiftKey) {
Expand All @@ -29,6 +29,20 @@ export function ChatInput({
}
};

// Auto-resize textarea based on content
const adjustHeight = () => {
const textarea = inputRef.current;
if (textarea) {
textarea.style.height = "auto";
textarea.style.height = `${Math.min(textarea.scrollHeight, 200)}px`;
}
};

// Adjust height when value changes
useEffect(() => {
adjustHeight();
}, [value]);

// Auto-focus input after message submission (when loading completes)
useEffect(() => {
if (!isLoading && inputRef.current) {
Expand All @@ -39,7 +53,7 @@ export function ChatInput({
return (
<div
className={cn(
"relative flex items-center gap-2 p-2 rounded-full",
"relative flex items-end gap-2 p-2 rounded-2xl",
// Glassmorphism
"bg-white/70 dark:bg-white/5 backdrop-blur-xl",
"border border-white/60 dark:border-white/10",
Expand All @@ -48,16 +62,16 @@ export function ChatInput({
className
)}
>
<input
<textarea
ref={inputRef}
type="text"
value={value}
onChange={(e) => onChange(e.target.value)}
onKeyDown={handleKeyDown}
placeholder={placeholder}
disabled={isLoading}
rows={1}
className={cn(
"flex-1 bg-transparent px-4 py-2 text-base",
"flex-1 bg-transparent px-4 py-2 text-base min-h-[44px] max-h-[200px] resize-none",
"placeholder:text-muted-foreground/60",
"focus:outline-none",
"disabled:opacity-50"
Expand Down
23 changes: 18 additions & 5 deletions extension/src/sidebar/ChatSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export function ChatSidebar({ pageContent, pageUrl, pageTitle, onClose }: ChatSi
// Follow-up suggestions from LLM
const [followupSuggestions, setFollowupSuggestions] = useState<string[]>([]);
const messagesEndRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
const inputRef = useRef<HTMLTextAreaElement>(null);

const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
Expand All @@ -101,6 +101,19 @@ export function ChatSidebar({ pageContent, pageUrl, pageTitle, onClose }: ChatSi
inputRef.current?.focus();
}, []);

// Auto-resize textarea based on content
const adjustHeight = () => {
const textarea = inputRef.current;
if (textarea) {
textarea.style.height = 'auto';
textarea.style.height = `${Math.min(textarea.scrollHeight, 200)}px`;
}
};

useEffect(() => {
adjustHeight();
}, [input]);

const sendMessage = async (messageText?: string) => {
const userMessage = (messageText || input).trim();
if (!userMessage || loading) return;
Expand Down Expand Up @@ -420,16 +433,16 @@ export function ChatSidebar({ pageContent, pageUrl, pageTitle, onClose }: ChatSi

{/* Input */}
<div className="p-3 border-t border-border">
<div className="relative flex items-center gap-2 p-2 rounded-full bg-white/70 dark:bg-white/5 backdrop-blur-xl border border-white/60 dark:border-white/10 shadow-lg shadow-black/5 dark:shadow-black/20">
<input
<div className="relative flex items-end gap-2 p-2 rounded-2xl bg-white/70 dark:bg-white/5 backdrop-blur-xl border border-white/60 dark:border-white/10 shadow-lg shadow-black/5 dark:shadow-black/20">
<textarea
ref={inputRef}
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Ask about this page..."
disabled={loading}
className="flex-1 bg-transparent px-4 py-2 text-base placeholder:text-muted-foreground/60 focus:outline-none disabled:opacity-50"
rows={1}
className="flex-1 bg-transparent px-4 py-2 text-base min-h-[44px] max-h-[200px] resize-none placeholder:text-muted-foreground/60 focus:outline-none disabled:opacity-50"
/>
<Button
size="icon"
Expand Down