Skip to content

Commit 5fe6348

Browse files
improvement(knowledge): inline field validation, toast for server errors
1 parent 815278f commit 5fe6348

2 files changed

Lines changed: 61 additions & 32 deletions

File tree

apps/sim/app/workspace/[workspaceId]/knowledge/components/create-base-modal/create-base-modal.tsx

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { createLogger } from '@sim/logger'
2222
import { getErrorMessage } from '@sim/utils/errors'
2323
import { X } from 'lucide-react'
2424
import { useParams } from 'next/navigation'
25-
import { type FieldErrors, useForm } from 'react-hook-form'
25+
import { useForm } from 'react-hook-form'
2626
import { z } from 'zod'
2727
import type { StrategyOptions } from '@/lib/chunkers/types'
2828
import { KNOWLEDGE_BASE_DESCRIPTION_MAX_LENGTH } from '@/lib/knowledge/constants'
@@ -142,11 +142,14 @@ export const CreateBaseModal = memo(function CreateBaseModal({
142142
onOpenChange(open)
143143
}
144144

145-
const { register, handleSubmit, reset, watch, setValue } = useForm<
146-
FormInputValues,
147-
unknown,
148-
FormValues
149-
>({
145+
const {
146+
register,
147+
handleSubmit,
148+
reset,
149+
watch,
150+
setValue,
151+
formState: { errors },
152+
} = useForm<FormInputValues, unknown, FormValues>({
150153
resolver: zodResolver(FormSchema),
151154
defaultValues: {
152155
name: '',
@@ -220,15 +223,6 @@ export const CreateBaseModal = memo(function CreateBaseModal({
220223
const isSubmitting =
221224
createKnowledgeBaseMutation.isPending || deleteKnowledgeBaseMutation.isPending || isUploading
222225

223-
const onInvalid = (formErrors: FieldErrors<FormInputValues>) => {
224-
const firstMessage = Object.values(formErrors).find(
225-
(fieldError) => typeof fieldError?.message === 'string'
226-
)?.message
227-
toast.error(
228-
typeof firstMessage === 'string' ? firstMessage : 'Please fix the errors and try again'
229-
)
230-
}
231-
232226
const onSubmit = async (data: FormValues) => {
233227
try {
234228
const strategyOptions: StrategyOptions | undefined =
@@ -293,7 +287,7 @@ export const CreateBaseModal = memo(function CreateBaseModal({
293287
<ChipModal open={open} onOpenChange={handleClose} srTitle='Create Knowledge Base' size='lg'>
294288
<ChipModalHeader onClose={() => handleClose(false)}>Create Knowledge Base</ChipModalHeader>
295289

296-
<form onSubmit={handleSubmit(onSubmit, onInvalid)} className='flex min-h-0 flex-1 flex-col'>
290+
<form onSubmit={handleSubmit(onSubmit)} className='flex min-h-0 flex-1 flex-col'>
297291
<button type='submit' hidden disabled={isSubmitting || !nameValue?.trim()} />
298292
<ChipModalBody>
299293
<input
@@ -305,10 +299,11 @@ export const CreateBaseModal = memo(function CreateBaseModal({
305299
readOnly
306300
/>
307301

308-
<ChipModalField type='custom' title='Name'>
302+
<ChipModalField type='custom' title='Name' error={errors.name?.message}>
309303
<ChipInput
310304
placeholder='Enter knowledge base name'
311305
{...register('name')}
306+
error={Boolean(errors.name)}
312307
autoComplete='off'
313308
autoCorrect='off'
314309
autoCapitalize='off'
@@ -317,36 +312,49 @@ export const CreateBaseModal = memo(function CreateBaseModal({
317312
/>
318313
</ChipModalField>
319314

320-
<ChipModalField type='custom' title='Description'>
315+
<ChipModalField type='custom' title='Description' error={errors.description?.message}>
321316
<ChipTextarea
322317
placeholder='Describe this knowledge base (optional)'
323318
rows={4}
324319
{...register('description')}
320+
error={Boolean(errors.description)}
325321
/>
326322
</ChipModalField>
327323

328324
<div className='flex gap-3'>
329-
<ChipModalField type='custom' title='Min Chunk Size (characters)' className='flex-1'>
325+
<ChipModalField
326+
type='custom'
327+
title='Min Chunk Size (characters)'
328+
className='flex-1'
329+
error={errors.minChunkSize?.message}
330+
>
330331
<ChipInput
331332
type='number'
332333
min={1}
333334
max={2000}
334335
step={1}
335336
placeholder='100'
336337
{...register('minChunkSize', { valueAsNumber: true })}
338+
error={Boolean(errors.minChunkSize)}
337339
autoComplete='off'
338340
data-form-type='other'
339341
/>
340342
</ChipModalField>
341343

342-
<ChipModalField type='custom' title='Max Chunk Size (tokens)' className='flex-1'>
344+
<ChipModalField
345+
type='custom'
346+
title='Max Chunk Size (tokens)'
347+
className='flex-1'
348+
error={errors.maxChunkSize?.message}
349+
>
343350
<ChipInput
344351
type='number'
345352
min={100}
346353
max={4000}
347354
step={1}
348355
placeholder='1024'
349356
{...register('maxChunkSize', { valueAsNumber: true })}
357+
error={Boolean(errors.maxChunkSize)}
350358
autoComplete='off'
351359
data-form-type='other'
352360
/>
@@ -357,6 +365,7 @@ export const CreateBaseModal = memo(function CreateBaseModal({
357365
type='custom'
358366
title='Overlap (tokens)'
359367
hint='1 token ≈ 4 characters. Max chunk size and overlap are in tokens.'
368+
error={errors.overlapSize?.message}
360369
>
361370
<ChipInput
362371
type='number'
@@ -365,6 +374,7 @@ export const CreateBaseModal = memo(function CreateBaseModal({
365374
step={1}
366375
placeholder='200'
367376
{...register('overlapSize', { valueAsNumber: true })}
377+
error={Boolean(errors.overlapSize)}
368378
autoComplete='off'
369379
data-form-type='other'
370380
/>
@@ -390,10 +400,12 @@ export const CreateBaseModal = memo(function CreateBaseModal({
390400
type='custom'
391401
title='Regex Pattern'
392402
hint='Text will be split at each match of this regex pattern.'
403+
error={errors.regexPattern?.message}
393404
>
394405
<ChipInput
395406
placeholder='e.g. \\n\\n or (?<=\\})\\s*(?=\\{)'
396407
{...register('regexPattern')}
408+
error={Boolean(errors.regexPattern)}
397409
autoComplete='off'
398410
data-form-type='other'
399411
/>
@@ -512,7 +524,7 @@ export const CreateBaseModal = memo(function CreateBaseModal({
512524
: 'Creating...'
513525
: 'Creating...'
514526
: 'Create',
515-
onClick: handleSubmit(onSubmit, onInvalid),
527+
onClick: handleSubmit(onSubmit),
516528
disabled: isSubmitting || !nameValue?.trim(),
517529
}}
518530
/>

apps/sim/app/workspace/[workspaceId]/knowledge/components/edit-knowledge-base-modal/edit-knowledge-base-modal.tsx

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
ChipModalField,
88
ChipModalFooter,
99
ChipModalHeader,
10-
toast,
1110
} from '@sim/emcn'
1211
import { createLogger } from '@sim/logger'
1312
import { KNOWLEDGE_BASE_DESCRIPTION_MAX_LENGTH } from '@/lib/knowledge/constants'
@@ -39,6 +38,8 @@ export const EditKnowledgeBaseModal = memo(function EditKnowledgeBaseModal({
3938
}: EditKnowledgeBaseModalProps) {
4039
const [name, setName] = useState(initialName)
4140
const [description, setDescription] = useState(initialDescription)
41+
const [nameError, setNameError] = useState<string | null>(null)
42+
const [descriptionError, setDescriptionError] = useState<string | null>(null)
4243
const [isSubmitting, setIsSubmitting] = useState(false)
4344

4445
/**
@@ -51,24 +52,38 @@ export const EditKnowledgeBaseModal = memo(function EditKnowledgeBaseModal({
5152
if (open) {
5253
setName(initialName)
5354
setDescription(initialDescription)
55+
setNameError(null)
56+
setDescriptionError(null)
5457
}
5558
}
5659

57-
const validate = (): string | null => {
58-
if (!name.trim()) return 'Name is required'
59-
if (name.trim().length > 100) return 'Name must be less than 100 characters'
60+
const validate = (): boolean => {
61+
let valid = true
62+
63+
if (!name.trim()) {
64+
setNameError('Name is required')
65+
valid = false
66+
} else if (name.trim().length > 100) {
67+
setNameError('Name must be less than 100 characters')
68+
valid = false
69+
} else {
70+
setNameError(null)
71+
}
72+
6073
if (description.length > KNOWLEDGE_BASE_DESCRIPTION_MAX_LENGTH) {
61-
return `Description must be ${KNOWLEDGE_BASE_DESCRIPTION_MAX_LENGTH} characters or less`
74+
setDescriptionError(
75+
`Description must be ${KNOWLEDGE_BASE_DESCRIPTION_MAX_LENGTH} characters or less`
76+
)
77+
valid = false
78+
} else {
79+
setDescriptionError(null)
6280
}
63-
return null
81+
82+
return valid
6483
}
6584

6685
const handleSubmit = async () => {
67-
const validationError = validate()
68-
if (validationError) {
69-
toast.error(validationError)
70-
return
71-
}
86+
if (!validate()) return
7287

7388
setIsSubmitting(true)
7489

@@ -96,6 +111,7 @@ export const EditKnowledgeBaseModal = memo(function EditKnowledgeBaseModal({
96111
onChange={setName}
97112
placeholder='Enter knowledge base name'
98113
required
114+
error={nameError ?? undefined}
99115
autoComplete='off'
100116
/>
101117
<ChipModalField
@@ -105,6 +121,7 @@ export const EditKnowledgeBaseModal = memo(function EditKnowledgeBaseModal({
105121
onChange={setDescription}
106122
placeholder='Describe this knowledge base (optional)'
107123
rows={4}
124+
error={descriptionError ?? undefined}
108125
/>
109126
{chunkingConfig && (
110127
<ChipModalField type='custom' title='Chunking Configuration'>

0 commit comments

Comments
 (0)