Replace Input with auto-resizing Textarea in RetrievalTesting

• Add new Textarea component
• Auto-resize between 40px-120px height
• Support Enter to submit, Shift+Enter for newline
• Add form autocomplete attributes
• Reset height after message submission
This commit is contained in:
yangdx 2025-09-22 02:01:39 +08:00
parent 17dd56e41c
commit d2029fd804
2 changed files with 57 additions and 5 deletions

View file

@ -0,0 +1,25 @@
import * as React from 'react'
import { cn } from '@/lib/utils'
export interface TextareaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
className?: string
}
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, ...props }, ref) => {
return (
<textarea
className={cn(
'border-input file:text-foreground placeholder:text-muted-foreground focus-visible:ring-ring flex min-h-[60px] w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-1 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm resize-none',
className
)}
ref={ref}
{...props}
/>
)
}
)
Textarea.displayName = 'Textarea'
export default Textarea

View file

@ -1,4 +1,4 @@
import Input from '@/components/ui/Input' import Textarea from '@/components/ui/Textarea'
import Button from '@/components/ui/Button' import Button from '@/components/ui/Button'
import { useCallback, useEffect, useRef, useState } from 'react' import { useCallback, useEffect, useRef, useState } from 'react'
import { throttle } from '@/lib/utils' import { throttle } from '@/lib/utils'
@ -116,6 +116,7 @@ export default function RetrievalTesting() {
const [inputValue, setInputValue] = useState('') const [inputValue, setInputValue] = useState('')
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
const [inputError, setInputError] = useState('') // Error message for input const [inputError, setInputError] = useState('') // Error message for input
const textareaRef = useRef<HTMLTextAreaElement>(null)
// Reference to track if we should follow scroll during streaming (using ref for synchronous updates) // Reference to track if we should follow scroll during streaming (using ref for synchronous updates)
const shouldFollowScrollRef = useRef(true) const shouldFollowScrollRef = useRef(true)
const thinkingStartTime = useRef<number | null>(null) const thinkingStartTime = useRef<number | null>(null)
@ -229,6 +230,11 @@ export default function RetrievalTesting() {
setInputValue('') setInputValue('')
setIsLoading(true) setIsLoading(true)
// Reset textarea height to minimum after clearing input
if (textareaRef.current) {
textareaRef.current.style.height = '40px'
}
// Create a function to update the assistant's message // Create a function to update the assistant's message
const updateAssistantMessage = (chunk: string, isError?: boolean) => { const updateAssistantMessage = (chunk: string, isError?: boolean) => {
assistantMessage.content += chunk assistantMessage.content += chunk
@ -504,7 +510,7 @@ export default function RetrievalTesting() {
</div> </div>
</div> </div>
<form onSubmit={handleSubmit} className="flex shrink-0 items-center gap-2"> <form onSubmit={handleSubmit} className="flex shrink-0 items-center gap-2" autoComplete="on">
<Button <Button
type="button" type="button"
variant="outline" variant="outline"
@ -519,16 +525,37 @@ export default function RetrievalTesting() {
<label htmlFor="query-input" className="sr-only"> <label htmlFor="query-input" className="sr-only">
{t('retrievePanel.retrieval.placeholder')} {t('retrievePanel.retrieval.placeholder')}
</label> </label>
<Input <Textarea
ref={textareaRef}
id="query-input" id="query-input"
className="w-full" name="query"
autoComplete="on"
className="w-full min-h-[40px] max-h-[120px] overflow-y-auto"
value={inputValue} value={inputValue}
onChange={(e) => { onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
setInputValue(e.target.value) setInputValue(e.target.value)
if (inputError) setInputError('') if (inputError) setInputError('')
}} }}
onKeyDown={(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault()
handleSubmit(e as any)
}
}}
placeholder={t('retrievePanel.retrieval.placeholder')} placeholder={t('retrievePanel.retrieval.placeholder')}
disabled={isLoading} disabled={isLoading}
rows={1}
style={{
resize: 'none',
height: 'auto',
minHeight: '40px',
maxHeight: '120px'
}}
onInput={(e: React.FormEvent<HTMLTextAreaElement>) => {
const target = e.target as HTMLTextAreaElement
target.style.height = 'auto'
target.style.height = Math.min(target.scrollHeight, 120) + 'px'
}}
/> />
{/* Error message below input */} {/* Error message below input */}
{inputError && ( {inputError && (