mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-03-07 00:26:42 +08:00
Fix: enable chat input resizing (#12998)
## Summary - add resizable support to shared textarea component - enable vertical resizing for chat inputs in chat and share surfaces - preserve autosize behavior while honoring manual resize height ## Test plan - not run (not requested) Fixes #12803 --------- Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -56,6 +56,7 @@ interface NextMessageInputProps {
|
||||
removeFile?(file: File): void;
|
||||
showReasoning?: boolean;
|
||||
showInternet?: boolean;
|
||||
resize?: 'none' | 'vertical' | 'horizontal' | 'both';
|
||||
}
|
||||
|
||||
export function NextMessageInput({
|
||||
@ -65,6 +66,7 @@ export function NextMessageInput({
|
||||
sendLoading,
|
||||
disabled,
|
||||
showUploadIcon = true,
|
||||
resize = 'none',
|
||||
onUpload,
|
||||
onInputChange,
|
||||
stopOutputMessage,
|
||||
@ -211,6 +213,7 @@ export function NextMessageInput({
|
||||
disabled={isUploading || disabled || sendLoading}
|
||||
onKeyDown={handleKeyDown}
|
||||
autoSize={{ minRows: 1, maxRows: 8 }}
|
||||
resize={resize}
|
||||
/>
|
||||
<div className={cn('flex items-center justify-between gap-1.5')}>
|
||||
<div className="flex items-center gap-3">
|
||||
|
||||
@ -16,25 +16,37 @@ interface TextareaProps
|
||||
minRows?: number;
|
||||
maxRows?: number;
|
||||
};
|
||||
resize?: 'none' | 'vertical' | 'horizontal' | 'both';
|
||||
}
|
||||
const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
({ className, autoSize, ...props }, ref) => {
|
||||
({ className, autoSize, resize = 'none', ...props }, ref) => {
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const manualHeightRef = useRef<number | null>(null);
|
||||
const isAdjustingRef = useRef(false);
|
||||
const getLineHeight = (element: HTMLElement): number => {
|
||||
const style = window.getComputedStyle(element);
|
||||
return parseInt(style.lineHeight, 10) || 20;
|
||||
};
|
||||
const adjustHeight = useCallback(() => {
|
||||
if (!textareaRef.current) return;
|
||||
if (!textareaRef.current || !autoSize) return;
|
||||
const lineHeight = getLineHeight(textareaRef.current);
|
||||
const maxHeight = (autoSize?.maxRows || 3) * lineHeight;
|
||||
|
||||
isAdjustingRef.current = true;
|
||||
textareaRef.current.style.height = 'auto';
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if (!textareaRef.current) return;
|
||||
|
||||
const scrollHeight = textareaRef.current.scrollHeight;
|
||||
textareaRef.current.style.height = `${Math.min(scrollHeight, maxHeight)}px`;
|
||||
const desiredHeight = Math.min(scrollHeight, maxHeight);
|
||||
const manualHeight = manualHeightRef.current;
|
||||
const nextHeight =
|
||||
manualHeight && manualHeight > desiredHeight
|
||||
? manualHeight
|
||||
: desiredHeight;
|
||||
textareaRef.current.style.height = `${nextHeight}px`;
|
||||
isAdjustingRef.current = false;
|
||||
});
|
||||
}, [autoSize]);
|
||||
|
||||
@ -51,18 +63,42 @@ const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
ref.current = textareaRef.current;
|
||||
}
|
||||
}, [ref]);
|
||||
useEffect(() => {
|
||||
if (!textareaRef.current || !autoSize || resize === 'none') {
|
||||
manualHeightRef.current = null;
|
||||
return;
|
||||
}
|
||||
const element = textareaRef.current;
|
||||
let prevHeight = element.getBoundingClientRect().height;
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
if (isAdjustingRef.current) return;
|
||||
const entry = entries[0];
|
||||
if (!entry) return;
|
||||
const nextHeight = entry.contentRect.height;
|
||||
if (Math.abs(nextHeight - prevHeight) > 1) {
|
||||
manualHeightRef.current = nextHeight;
|
||||
}
|
||||
prevHeight = nextHeight;
|
||||
});
|
||||
observer.observe(element);
|
||||
return () => observer.disconnect();
|
||||
}, [autoSize, resize]);
|
||||
|
||||
const resizable = resize !== 'none';
|
||||
|
||||
return (
|
||||
<textarea
|
||||
className={cn(
|
||||
'flex min-h-[80px] w-full bg-bg-input rounded-md border border-border-button px-3 py-2 text-base ring-offset-background placeholder:text-text-disabled focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-accent-primary focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm overflow-hidden',
|
||||
'flex min-h-[80px] w-full bg-bg-input rounded-md border border-border-button px-3 py-2 text-base ring-offset-background placeholder:text-text-disabled focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-accent-primary focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
||||
resizable ? 'overflow-auto' : 'overflow-hidden',
|
||||
className,
|
||||
)}
|
||||
rows={autoSize?.minRows ?? props.rows ?? undefined}
|
||||
style={{
|
||||
maxHeight: autoSize?.maxRows
|
||||
maxHeight: autoSize?.maxRows && !resizable
|
||||
? `${autoSize.maxRows * 20}px`
|
||||
: undefined,
|
||||
overflow: autoSize ? 'auto' : undefined,
|
||||
resize,
|
||||
}}
|
||||
ref={textareaRef}
|
||||
{...props}
|
||||
|
||||
@ -127,6 +127,7 @@ function AgentChatBox() {
|
||||
disabled={isWaitting}
|
||||
sendDisabled={sendLoading || isWaitting}
|
||||
isUploading={loading || isWaitting}
|
||||
resize="vertical"
|
||||
onPressEnter={handlePressEnter}
|
||||
onInputChange={handleInputChange}
|
||||
stopOutputMessage={stopOutputMessage}
|
||||
|
||||
@ -193,6 +193,7 @@ const ChatContainer = () => {
|
||||
value={value}
|
||||
disabled={hasError || isWaitting}
|
||||
sendDisabled={sendDisabled || isWaitting}
|
||||
resize="vertical"
|
||||
conversationId={conversationId}
|
||||
onInputChange={handleInputChange}
|
||||
onPressEnter={handlePressEnter}
|
||||
|
||||
@ -255,6 +255,7 @@ export function MultipleChatBox({
|
||||
sendDisabled={sendDisabled}
|
||||
sendLoading={sendLoading}
|
||||
value={value}
|
||||
resize="vertical"
|
||||
onInputChange={handleInputChange}
|
||||
onPressEnter={handlePressEnter}
|
||||
conversationId={conversationId}
|
||||
|
||||
@ -112,6 +112,7 @@ export function SingleChatBox({
|
||||
sendDisabled={sendDisabled}
|
||||
sendLoading={sendLoading}
|
||||
value={value}
|
||||
resize="vertical"
|
||||
onInputChange={handleInputChange}
|
||||
onPressEnter={handlePressEnter}
|
||||
conversationId={conversationId}
|
||||
|
||||
@ -116,6 +116,7 @@ const ChatContainer = () => {
|
||||
value={value}
|
||||
disabled={hasError}
|
||||
sendDisabled={sendDisabled}
|
||||
resize="vertical"
|
||||
conversationId={conversationId}
|
||||
onInputChange={handleInputChange}
|
||||
onPressEnter={handlePressEnter}
|
||||
|
||||
Reference in New Issue
Block a user