diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-importer.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-importer.tsx index 8f25583962..6cccc4ba4e 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-importer.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-importer.tsx @@ -4,7 +4,7 @@ import cn from '@/utils/classnames' import { useTranslation } from 'react-i18next' import { RiCloseLine } from '@remixicon/react' import Button from '@/app/components/base/button' -import { checkDepth } from '../../utils' +import { checkJsonDepth } from '../../utils' import { JSON_SCHEMA_MAX_DEPTH } from '@/config' import CodeEditor from './code-editor' import ErrorMessage from './error-message' @@ -59,7 +59,7 @@ const JsonImporter: FC = ({ setParseError(new Error('Root must be an object, not an array or primitive value.')) return } - const maxDepth = checkDepth(parsedJSON) + const maxDepth = checkJsonDepth(parsedJSON) if (maxDepth > JSON_SCHEMA_MAX_DEPTH) { setParseError({ type: 'error', @@ -72,10 +72,10 @@ const JsonImporter: FC = ({ setOpen(false) } catch (e: any) { - if (e instanceof SyntaxError) + if (e instanceof Error) setParseError(e) else - setParseError(new Error('Unknown error')) + setParseError(new Error('Invalid JSON')) } }, [onSubmit, json]) diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-config.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-config.tsx index bbc8f567f9..b463a4d299 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-config.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-config.tsx @@ -9,12 +9,20 @@ import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import VisualEditor from './visual-editor' import SchemaEditor from './schema-editor' -import { convertBooleanToString, getValidationErrorMessage, jsonToSchema, validateSchemaAgainstDraft7 } from '../../utils' +import { + checkJsonSchemaDepth, + convertBooleanToString, + getValidationErrorMessage, + jsonToSchema, + preValidateSchema, + validateSchemaAgainstDraft7, +} from '../../utils' import { MittProvider, VisualEditorContextProvider } from './visual-editor/context' import ErrorMessage from './error-message' import { useVisualEditorStore } from './visual-editor/store' import Toast from '@/app/components/base/toast' import { useGetLanguage } from '@/context/i18n' +import { JSON_SCHEMA_MAX_DEPTH } from '@/config' type JsonSchemaConfigProps = { defaultSchema?: SchemaRoot @@ -74,18 +82,26 @@ const JsonSchemaConfig: FC = ({ if (currentTab === value) return if (currentTab === SchemaView.JsonSchema) { try { - const parsedJson = JSON.parse(json) - const schema = convertBooleanToString(parsedJson) + const schema = JSON.parse(json) setParseError(null) - const ajvError = validateSchemaAgainstDraft7(schema) - if (ajvError.length > 0) { - setValidationError(getValidationErrorMessage(ajvError)) + const result = preValidateSchema(schema) + if (!result.success) { + setValidationError(result.error.message) return } - else { - setJsonSchema(schema) - setValidationError('') + const schemaDepth = checkJsonSchemaDepth(schema) + if (schemaDepth > JSON_SCHEMA_MAX_DEPTH) { + setValidationError(`Schema exceeds maximum depth of ${JSON_SCHEMA_MAX_DEPTH}.`) + return } + convertBooleanToString(schema) + const validationErrors = validateSchemaAgainstDraft7(schema) + if (validationErrors.length > 0) { + setValidationError(getValidationErrorMessage(validationErrors)) + return + } + setJsonSchema(schema) + setValidationError('') } catch (error) { setValidationError('') @@ -135,7 +151,7 @@ const JsonSchemaConfig: FC = ({ const handleResetDefaults = useCallback(() => { if (currentTab === SchemaView.VisualEditor) { - setHoveringProperty('') + setHoveringProperty(null) advancedEditing && setAdvancedEditing(false) isAddingNewField && setIsAddingNewField(false) } @@ -153,15 +169,24 @@ const JsonSchemaConfig: FC = ({ try { schema = JSON.parse(json) setParseError(null) - const ajvError = validateSchemaAgainstDraft7(schema) - if (ajvError.length > 0) { - setValidationError(getValidationErrorMessage(ajvError)) + const result = preValidateSchema(schema) + if (!result.success) { + setValidationError(result.error.message) return } - else { - setJsonSchema(schema) - setValidationError('') + const schemaDepth = checkJsonSchemaDepth(schema) + if (schemaDepth > JSON_SCHEMA_MAX_DEPTH) { + setValidationError(`Schema exceeds maximum depth of ${JSON_SCHEMA_MAX_DEPTH}.`) + return } + convertBooleanToString(schema) + const validationErrors = validateSchemaAgainstDraft7(schema) + if (validationErrors.length > 0) { + setValidationError(getValidationErrorMessage(validationErrors)) + return + } + setJsonSchema(schema) + setValidationError('') } catch (error) { setValidationError('') diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/generated-result.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/generated-result.tsx index 5bb64938a4..00f57237e5 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/generated-result.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/generated-result.tsx @@ -47,14 +47,13 @@ const GeneratedResult: FC = ({ const jsonSchema = useMemo(() => formatJSON(schema), [schema]) const handleApply = useCallback(() => { - const ajvError = validateSchemaAgainstDraft7(schema) - if (ajvError.length > 0) { - setValidationError(getValidationErrorMessage(ajvError)) - } - else { - onApply() - setValidationError('') + const validationErrors = validateSchemaAgainstDraft7(schema) + if (validationErrors.length > 0) { + setValidationError(getValidationErrorMessage(validationErrors)) + return } + onApply() + setValidationError('') }, [schema, onApply]) return ( diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/index.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/index.tsx index 7425facb79..4935d9bc51 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/index.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/index.tsx @@ -47,6 +47,15 @@ const TYPE_OPTIONS = [ { value: ArrayType.object, text: 'array[object]' }, ] +const MAXIMUM_DEPTH_TYPE_OPTIONS = [ + { value: Type.string, text: 'string' }, + { value: Type.number, text: 'number' }, + // { value: Type.boolean, text: 'boolean' }, + { value: ArrayType.string, text: 'array[string]' }, + { value: ArrayType.number, text: 'array[number]' }, + // { value: ArrayType.boolean, text: 'array[boolean]' }, +] + const EditCard: FC = ({ fields, depth, @@ -63,7 +72,8 @@ const EditCard: FC = ({ const { emit, useSubscribe } = useMittContext() const blurWithActions = useRef(false) - const disableAddBtn = depth >= JSON_SCHEMA_MAX_DEPTH || (currentFields.type !== Type.object && currentFields.type !== ArrayType.object) + const maximumDepthReached = depth === JSON_SCHEMA_MAX_DEPTH + const disableAddBtn = maximumDepthReached || (currentFields.type !== Type.object && currentFields.type !== ArrayType.object) const hasAdvancedOptions = currentFields.type === Type.string || currentFields.type === Type.number const isAdvancedEditing = advancedEditing || isAddingNewField @@ -205,7 +215,7 @@ const EditCard: FC = ({ /> diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/index.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/index.tsx index a94fa09a2c..1df42532a6 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/index.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/index.tsx @@ -19,7 +19,7 @@ const VisualEditor: FC = (props) => { schema={schema} required={false} path={[]} - depth={1} + depth={0} /> ) diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/schema-node.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/schema-node.tsx index 67cd900cd4..9fd9e37820 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/schema-node.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/schema-node.tsx @@ -23,28 +23,31 @@ type SchemaNodeProps = { // Support 10 levels of indentation const indentPadding: Record = { - 1: 'pl-0', - 2: 'pl-[20px]', - 3: 'pl-[40px]', - 4: 'pl-[60px]', - 5: 'pl-[80px]', - 6: 'pl-[100px]', - 7: 'pl-[120px]', - 8: 'pl-[140px]', - 9: 'pl-[160px]', - 10: 'pl-[180px]', + 0: 'pl-0', + 1: 'pl-[20px]', + 2: 'pl-[40px]', + 3: 'pl-[60px]', + 4: 'pl-[80px]', + 5: 'pl-[100px]', + 6: 'pl-[120px]', + 7: 'pl-[140px]', + 8: 'pl-[160px]', + 9: 'pl-[180px]', + 10: 'pl-[200px]', } const indentLeft: Record = { - 2: 'left-0', - 3: 'left-[20px]', - 4: 'left-[40px]', - 5: 'left-[60px]', - 6: 'left-[80px]', - 7: 'left-[100px]', - 8: 'left-[120px]', - 9: 'left-[140px]', - 10: 'left-[160px]', + 0: 'left-0', + 1: 'left-[20px]', + 2: 'left-[40px]', + 3: 'left-[60px]', + 4: 'left-[80px]', + 5: 'left-[100px]', + 6: 'left-[120px]', + 7: 'left-[140px]', + 8: 'left-[160px]', + 9: 'left-[180px]', + 10: 'left-[200px]', } const SchemaNode: FC = ({ @@ -61,13 +64,13 @@ const SchemaNode: FC = ({ const isAddingNewField = useVisualEditorStore(state => state.isAddingNewField) const advancedEditing = useVisualEditorStore(state => state.advancedEditing) - const { run: setHoveringPropertyDebounced } = useDebounceFn((path: string) => { + const { run: setHoveringPropertyDebounced } = useDebounceFn((path: string | null) => { setHoveringProperty(path) }, { wait: 50 }) const hasChildren = useMemo(() => getHasChildren(schema), [schema]) const type = useMemo(() => getFieldType(schema), [schema]) - const isHovering = hoveringProperty === path.join('.') && depth > 1 + const isHovering = hoveringProperty === path.join('.') const handleExpand = () => { setIsExpanded(!isExpanded) @@ -80,16 +83,16 @@ const SchemaNode: FC = ({ const handleMouseLeave = () => { if (advancedEditing || isAddingNewField) return - setHoveringPropertyDebounced('') + setHoveringPropertyDebounced(null) } return (
- {depth > 1 && hasChildren && ( + {depth > 0 && hasChildren && (