From 1c59a4f150a91b7a9e746b8890fdf6e00b75d204 Mon Sep 17 00:00:00 2001 From: chanx <1243304602@qq.com> Date: Tue, 4 Aug 2026 10:46:14 +0800 Subject: [PATCH] feat(message-input): add custom drag-to-resize handle at top border (#17746) --- web/src/components/message-input/next.tsx | 70 +++++++++++++++++++++-- 1 file changed, 65 insertions(+), 5 deletions(-) diff --git a/web/src/components/message-input/next.tsx b/web/src/components/message-input/next.tsx index f4dc9364a4..936cb88ed3 100644 --- a/web/src/components/message-input/next.tsx +++ b/web/src/components/message-input/next.tsx @@ -16,7 +16,7 @@ import { Button } from '@/components/ui/button'; import { Textarea } from '@/components/ui/textarea'; import { CircleStop, Globe, Paperclip, Send, Upload, X } from 'lucide-react'; import * as React from 'react'; -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { toast } from 'sonner'; import storage from '@/utils/authorization-util'; @@ -81,6 +81,56 @@ export function NextMessageInput({ ); const [enableInternet, setEnableInternet] = useState(false); + const textareaRef = useRef(null); + const [isManualMode, setIsManualMode] = useState(false); + const [isResizing, setIsResizing] = useState(false); + const dragStartRef = useRef({ startY: 0, startHeight: 0 }); + + const handleResizeStart = useCallback( + (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + const textarea = textareaRef.current; + if (!textarea) return; + dragStartRef.current = { + startY: e.clientY, + startHeight: textarea.getBoundingClientRect().height, + }; + setIsManualMode(true); + setIsResizing(true); + }, + [], + ); + + useEffect(() => { + if (!isResizing) return; + + const handleMouseMove = (e: MouseEvent) => { + const textarea = textareaRef.current; + if (!textarea) return; + const { startY, startHeight } = dragStartRef.current; + const delta = startY - e.clientY; + const newHeight = Math.max(40, Math.min(startHeight + delta, 400)); + textarea.style.height = `${newHeight}px`; + }; + + const handleMouseUp = () => { + setIsResizing(false); + }; + + document.body.style.cursor = 'ns-resize'; + document.body.style.userSelect = 'none'; + window.addEventListener('mousemove', handleMouseMove); + window.addEventListener('mouseup', handleMouseUp); + + return () => { + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + window.removeEventListener('mousemove', handleMouseMove); + window.removeEventListener('mouseup', handleMouseUp); + }; + }, [isResizing]); + const thinkingOptions = useMemo( () => [ { @@ -216,6 +266,16 @@ export function NextMessageInput({ has-[textarea:focus]:outline-accent-primary has-[textarea:focus]:outline-1 has-[textarea:focus]:outline-offset-2 " > + {resize === 'vertical' && ( + + )} ))} -