mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix(knowledge): surface KB description validation errors and raise limit to 10k (#5347)
This commit is contained in:
+22
-5
@@ -17,14 +17,16 @@ import {
|
||||
type ComboboxOption,
|
||||
cn,
|
||||
Loader,
|
||||
toast,
|
||||
} from '@sim/emcn'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { getErrorMessage } from '@sim/utils/errors'
|
||||
import { X } from 'lucide-react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { type FieldErrors, useForm } from 'react-hook-form'
|
||||
import { z } from 'zod'
|
||||
import type { StrategyOptions } from '@/lib/chunkers/types'
|
||||
import { KNOWLEDGE_BASE_DESCRIPTION_MAX_LENGTH } from '@/lib/knowledge/constants'
|
||||
import { formatFileSize, validateKnowledgeBaseFile } from '@/lib/uploads/utils/file-utils'
|
||||
import { ACCEPT_ATTRIBUTE } from '@/lib/uploads/utils/validation'
|
||||
import { useKnowledgeUpload } from '@/app/workspace/[workspaceId]/knowledge/hooks/use-knowledge-upload'
|
||||
@@ -58,7 +60,13 @@ const FormSchema = z
|
||||
.min(1, 'Name is required')
|
||||
.max(100, 'Name must be less than 100 characters')
|
||||
.refine((value) => value.trim().length > 0, 'Name cannot be empty'),
|
||||
description: z.string().max(500, 'Description must be less than 500 characters').optional(),
|
||||
description: z
|
||||
.string()
|
||||
.max(
|
||||
KNOWLEDGE_BASE_DESCRIPTION_MAX_LENGTH,
|
||||
`Description must be ${KNOWLEDGE_BASE_DESCRIPTION_MAX_LENGTH} characters or less`
|
||||
)
|
||||
.optional(),
|
||||
minChunkSize: z
|
||||
.number()
|
||||
.min(1, 'Min chunk size must be at least 1 character')
|
||||
@@ -223,6 +231,15 @@ export const CreateBaseModal = memo(function CreateBaseModal({
|
||||
const isSubmitting =
|
||||
createKnowledgeBaseMutation.isPending || deleteKnowledgeBaseMutation.isPending || isUploading
|
||||
|
||||
const onInvalid = (formErrors: FieldErrors<FormInputValues>) => {
|
||||
const firstMessage = Object.values(formErrors).find(
|
||||
(fieldError) => typeof fieldError?.message === 'string'
|
||||
)?.message
|
||||
toast.error(
|
||||
typeof firstMessage === 'string' ? firstMessage : 'Please fix the highlighted fields'
|
||||
)
|
||||
}
|
||||
|
||||
const onSubmit = async (data: FormValues) => {
|
||||
setSubmitStatus(null)
|
||||
|
||||
@@ -292,7 +309,7 @@ export const CreateBaseModal = memo(function CreateBaseModal({
|
||||
<ChipModal open={open} onOpenChange={handleClose} srTitle='Create Knowledge Base' size='lg'>
|
||||
<ChipModalHeader onClose={() => handleClose(false)}>Create Knowledge Base</ChipModalHeader>
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)} className='flex min-h-0 flex-1 flex-col'>
|
||||
<form onSubmit={handleSubmit(onSubmit, onInvalid)} className='flex min-h-0 flex-1 flex-col'>
|
||||
<button type='submit' hidden disabled={isSubmitting || !nameValue?.trim()} />
|
||||
<ChipModalBody>
|
||||
<input
|
||||
@@ -317,7 +334,7 @@ export const CreateBaseModal = memo(function CreateBaseModal({
|
||||
/>
|
||||
</ChipModalField>
|
||||
|
||||
<ChipModalField type='custom' title='Description'>
|
||||
<ChipModalField type='custom' title='Description' error={errors.description?.message}>
|
||||
<ChipTextarea
|
||||
placeholder='Describe this knowledge base (optional)'
|
||||
rows={4}
|
||||
@@ -520,7 +537,7 @@ export const CreateBaseModal = memo(function CreateBaseModal({
|
||||
: 'Creating...'
|
||||
: 'Creating...'
|
||||
: 'Create',
|
||||
onClick: handleSubmit(onSubmit),
|
||||
onClick: handleSubmit(onSubmit, onInvalid),
|
||||
disabled: isSubmitting || !nameValue?.trim(),
|
||||
}}
|
||||
/>
|
||||
|
||||
+16
-9
@@ -8,9 +8,11 @@ import {
|
||||
ChipModalField,
|
||||
ChipModalFooter,
|
||||
ChipModalHeader,
|
||||
toast,
|
||||
} from '@sim/emcn'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { getErrorMessage } from '@sim/utils/errors'
|
||||
import { KNOWLEDGE_BASE_DESCRIPTION_MAX_LENGTH } from '@/lib/knowledge/constants'
|
||||
import type { ChunkingConfig } from '@/lib/knowledge/types'
|
||||
|
||||
const logger = createLogger('EditKnowledgeBaseModal')
|
||||
@@ -60,31 +62,36 @@ export const EditKnowledgeBaseModal = memo(function EditKnowledgeBaseModal({
|
||||
}
|
||||
}
|
||||
|
||||
const validate = (): boolean => {
|
||||
let valid = true
|
||||
const validate = (): string | null => {
|
||||
let firstError: string | null = null
|
||||
|
||||
if (!name.trim()) {
|
||||
setNameError('Name is required')
|
||||
valid = false
|
||||
firstError ??= 'Name is required'
|
||||
} else if (name.trim().length > 100) {
|
||||
setNameError('Name must be less than 100 characters')
|
||||
valid = false
|
||||
firstError ??= 'Name must be less than 100 characters'
|
||||
} else {
|
||||
setNameError(null)
|
||||
}
|
||||
|
||||
if (description.length > 500) {
|
||||
setDescriptionError('Description must be less than 500 characters')
|
||||
valid = false
|
||||
if (description.length > KNOWLEDGE_BASE_DESCRIPTION_MAX_LENGTH) {
|
||||
const message = `Description must be ${KNOWLEDGE_BASE_DESCRIPTION_MAX_LENGTH} characters or less`
|
||||
setDescriptionError(message)
|
||||
firstError ??= message
|
||||
} else {
|
||||
setDescriptionError(null)
|
||||
}
|
||||
|
||||
return valid
|
||||
return firstError
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!validate()) return
|
||||
const validationError = validate()
|
||||
if (validationError) {
|
||||
toast.error(validationError)
|
||||
return
|
||||
}
|
||||
|
||||
setIsSubmitting(true)
|
||||
setError(null)
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from '@/lib/api/contracts/knowledge/shared'
|
||||
import { defineRouteContract } from '@/lib/api/contracts/types'
|
||||
import type { StrategyOptions } from '@/lib/chunkers/types'
|
||||
import { KNOWLEDGE_BASE_DESCRIPTION_MAX_LENGTH } from '@/lib/knowledge/constants'
|
||||
|
||||
export const knowledgeScopeSchema = z.enum(['active', 'archived', 'all'])
|
||||
export type KnowledgeScope = z.output<typeof knowledgeScopeSchema>
|
||||
@@ -51,7 +52,13 @@ export const chunkingConfigSchema = z
|
||||
|
||||
export const createKnowledgeBaseBodySchema = z.object({
|
||||
name: z.string().min(1, 'Name is required'),
|
||||
description: z.string().optional(),
|
||||
description: z
|
||||
.string()
|
||||
.max(
|
||||
KNOWLEDGE_BASE_DESCRIPTION_MAX_LENGTH,
|
||||
`Description must be ${KNOWLEDGE_BASE_DESCRIPTION_MAX_LENGTH} characters or less`
|
||||
)
|
||||
.optional(),
|
||||
workspaceId: z.string().min(1, 'Workspace ID is required'),
|
||||
embeddingModel: z.literal('text-embedding-3-small').default('text-embedding-3-small'),
|
||||
embeddingDimension: z.literal(1536).default(1536),
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
successResponseSchema,
|
||||
} from '@/lib/api/contracts/knowledge/shared'
|
||||
import { defineRouteContract } from '@/lib/api/contracts/types'
|
||||
import { KNOWLEDGE_BASE_DESCRIPTION_MAX_LENGTH } from '@/lib/knowledge/constants'
|
||||
|
||||
/**
|
||||
* Public API v1 schemas (`/api/v1/knowledge/**`)
|
||||
@@ -36,7 +37,13 @@ export const v1ListKnowledgeBasesQuerySchema = z.object({
|
||||
export const v1CreateKnowledgeBaseBodySchema = z.object({
|
||||
workspaceId: z.string().min(1, 'Workspace ID is required'),
|
||||
name: z.string().min(1, 'Name is required').max(255, 'Name must be 255 characters or less'),
|
||||
description: z.string().max(1000, 'Description must be 1000 characters or less').optional(),
|
||||
description: z
|
||||
.string()
|
||||
.max(
|
||||
KNOWLEDGE_BASE_DESCRIPTION_MAX_LENGTH,
|
||||
`Description must be ${KNOWLEDGE_BASE_DESCRIPTION_MAX_LENGTH} characters or less`
|
||||
)
|
||||
.optional(),
|
||||
chunkingConfig: v1ChunkingConfigSchema.optional().default({
|
||||
maxSize: 1024,
|
||||
minSize: 100,
|
||||
@@ -54,7 +61,13 @@ export const v1UpdateKnowledgeBaseBodySchema = z
|
||||
.object({
|
||||
workspaceId: z.string().min(1, 'Workspace ID is required'),
|
||||
name: z.string().min(1).max(255, 'Name must be 255 characters or less').optional(),
|
||||
description: z.string().max(1000, 'Description must be 1000 characters or less').optional(),
|
||||
description: z
|
||||
.string()
|
||||
.max(
|
||||
KNOWLEDGE_BASE_DESCRIPTION_MAX_LENGTH,
|
||||
`Description must be ${KNOWLEDGE_BASE_DESCRIPTION_MAX_LENGTH} characters or less`
|
||||
)
|
||||
.optional(),
|
||||
chunkingConfig: z
|
||||
.object({
|
||||
maxSize: z.number().min(100).max(4000),
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/** Max character length for a knowledge base description, enforced at every layer (UI, internal API, v1 API). */
|
||||
export const KNOWLEDGE_BASE_DESCRIPTION_MAX_LENGTH = 10_000
|
||||
|
||||
export const TAG_SLOT_CONFIG = {
|
||||
text: {
|
||||
slots: ['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6', 'tag7'] as const,
|
||||
|
||||
Reference in New Issue
Block a user