fix: disable submit button when prompt is empty

This commit is contained in:
yyh 2026-06-25 15:37:15 +08:00
parent 69e9fa68db
commit 68c3fc0ef6
No known key found for this signature in database
3 changed files with 18 additions and 10 deletions

View File

@ -533,14 +533,20 @@ describe('ChatInputArea', () => {
// -------------------------------------------------------------------------
describe('Validation & Constraints', () => {
it('should notify and NOT send when query is blank', async () => {
it('should disable send when query is blank', async () => {
const user = userEvent.setup({ delay: null })
const onSend = vi.fn()
render(<ChatInputArea onSend={onSend} visionConfig={mockVisionConfig} />)
await user.click(screen.getByRole('button', { name: 'common.operation.send' }))
const sendButton = screen.getByRole('button', { name: 'common.operation.send' })
expect(sendButton).toBeDisabled()
await user.click(sendButton)
expect(onSend).not.toHaveBeenCalled()
expect(mockNotify).toHaveBeenCalledWith(expect.objectContaining({ type: 'info' }))
expect(mockNotify).not.toHaveBeenCalled()
await user.type(getTextarea()!, ' ')
expect(sendButton).toBeDisabled()
})
it('should notify and NOT send while bot is responding', async () => {

View File

@ -54,6 +54,7 @@ const ChatInputArea = ({ readonly, botName, placeholder, showFeatureBar, showFil
const { t } = useTranslation()
const { wrapperRef, textareaRef, textValueRef, holdSpaceRef, handleTextareaResize, isMultipleLine } = useTextAreaHeight()
const [query, setQuery] = useState('')
const canSend = !!query.trim()
const [showVoiceInput, setShowVoiceInput] = useState(false)
const filesStore = useFileStore()
const { handleDragFileEnter, handleDragFileLeave, handleDragFileOver, handleDropFile, handleClipboardPasteFile, isDragActive } = useFile(visionConfig!, false)
@ -66,6 +67,9 @@ const ChatInputArea = ({ readonly, botName, placeholder, showFeatureBar, showFil
setTimeout(handleTextareaResize, 0)
}, [handleTextareaResize])
const handleSend = () => {
if (!canSend)
return
if (isResponding) {
toast.info(t('errorMessage.waitForResponse', { ns: 'appDebug' }))
return
@ -76,10 +80,6 @@ const ChatInputArea = ({ readonly, botName, placeholder, showFeatureBar, showFil
toast.info(t('errorMessage.waitForFileUpload', { ns: 'appDebug' }))
return
}
if (!query || !query.trim()) {
toast.info(t('errorMessage.queryRequired', { ns: 'appAnnotation' }))
return
}
if (checkInputsForm(inputs, inputsForm)) {
onSend(query, files)
handleQueryChange('')
@ -142,7 +142,7 @@ const ChatInputArea = ({ readonly, botName, placeholder, showFeatureBar, showFil
toast.error(t('voiceInput.notAllow', { ns: 'common' }))
})
}, [t])
const operation = (<Operation ref={holdSpaceRef} readonly={readonly} fileConfig={visionConfig} speechToTextConfig={speechToTextConfig} onShowVoiceInput={handleShowVoiceInput} onSend={handleSend} sendButtonLabel={sendButtonLabel} theme={theme} />)
const operation = (<Operation ref={holdSpaceRef} readonly={readonly} fileConfig={visionConfig} speechToTextConfig={speechToTextConfig} onShowVoiceInput={handleShowVoiceInput} onSend={handleSend} sendButtonLabel={sendButtonLabel} disabled={!canSend} theme={theme} />)
return (
<>
<div className={cn('relative z-10 overflow-hidden rounded-xl border border-components-chat-input-border bg-components-panel-bg-blur pb-[9px] shadow-md', isDragActive && 'border border-dashed border-components-option-card-option-selected-border', disabled && 'pointer-events-none border-components-panel-border opacity-50 shadow-none')}>

View File

@ -10,7 +10,6 @@ import {
RiMicLine,
RiSendPlane2Fill,
} from '@remixicon/react'
import { noop } from 'es-toolkit/function'
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
@ -23,6 +22,7 @@ type OperationProps = {
onShowVoiceInput?: () => void
onSend: () => void
sendButtonLabel?: string
disabled?: boolean
theme?: Theme | null
ref?: Ref<HTMLDivElement>
}
@ -34,6 +34,7 @@ const Operation: FC<OperationProps> = ({
onShowVoiceInput,
onSend,
sendButtonLabel,
disabled,
theme,
}) => {
const { t } = useTranslation()
@ -70,7 +71,8 @@ const Operation: FC<OperationProps> = ({
sendButtonLabel ? 'px-3' : 'w-8 px-0',
)}
variant="primary"
onClick={readonly ? noop : onSend}
disabled={readonly || disabled}
onClick={onSend}
style={
theme
? {