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:
parent
17dd56e41c
commit
d2029fd804
2 changed files with 57 additions and 5 deletions
25
lightrag_webui/src/components/ui/Textarea.tsx
Normal file
25
lightrag_webui/src/components/ui/Textarea.tsx
Normal 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
|
||||||
|
|
@ -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 && (
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue