feat: enhance JSON schema editor with keyboard shortcuts and additional properties validation

This commit is contained in:
twwu 2025-03-18 23:14:38 +08:00
parent 44be94d5b5
commit 86b1295efa
5 changed files with 41 additions and 12 deletions

View File

@ -1,6 +1,8 @@
import React, { type FC } from 'react' import React, { type FC } from 'react'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { getKeyboardKeyCodeBySystem, getKeyboardKeyNameBySystem } from '@/app/components/workflow/utils'
import { useKeyPress } from 'ahooks'
type AdvancedActionsProps = { type AdvancedActionsProps = {
isConfirmDisabled: boolean isConfirmDisabled: boolean
@ -8,6 +10,15 @@ type AdvancedActionsProps = {
onConfirm: () => void onConfirm: () => void
} }
const Key = (props: { keyName: string }) => {
const { keyName } = props
return (
<kbd className='flex items-center justify-center min-w-4 h-4 px-px rounded-[4px] bg-components-kbd-bg-white text-text-primary-on-surface system-kbd'>
{keyName}
</kbd>
)
}
const AdvancedActions: FC<AdvancedActionsProps> = ({ const AdvancedActions: FC<AdvancedActionsProps> = ({
isConfirmDisabled, isConfirmDisabled,
onCancel, onCancel,
@ -15,18 +26,31 @@ const AdvancedActions: FC<AdvancedActionsProps> = ({
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
useKeyPress([`${getKeyboardKeyCodeBySystem('ctrl')}.enter`], (e) => {
e.preventDefault()
onConfirm()
}, {
exactMatch: true,
useCapture: true,
})
return ( return (
<div className='flex items-center gap-x-1'> <div className='flex items-center gap-x-1'>
<Button size='small' variant='secondary' onClick={onCancel}> <Button size='small' variant='secondary' onClick={onCancel}>
{t('common.operation.cancel')} {t('common.operation.cancel')}
</Button> </Button>
<Button <Button
className='flex items-center gap-x-1'
disabled={isConfirmDisabled} disabled={isConfirmDisabled}
size='small' size='small'
variant='primary' variant='primary'
onClick={onConfirm} onClick={onConfirm}
> >
{t('common.operation.confirm')} <span>{t('common.operation.confirm')}</span>
<div className='flex items-center gap-x-0.5'>
<Key keyName={getKeyboardKeyNameBySystem('ctrl')} />
<Key keyName='⏎' />
</div>
</Button> </Button>
</div> </div>
) )

View File

@ -20,12 +20,12 @@ export type EditData = {
name: string name: string
type: Type | ArrayType type: Type | ArrayType
required: boolean required: boolean
description: string description?: string
enum?: SchemaEnumType enum?: SchemaEnumType
} }
type Options = { type Options = {
description: string description?: string
enum?: SchemaEnumType enum?: SchemaEnumType
} }

View File

@ -116,6 +116,7 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => {
schema.type = Type.object schema.type = Type.object
schema.properties = {} schema.properties = {}
schema.required = [] schema.required = []
schema.additionalProperties = false
break break
case ArrayType.string: case ArrayType.string:
schema.type = Type.array schema.type = Type.array
@ -141,6 +142,7 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => {
type: Type.object, type: Type.object,
properties: {}, properties: {},
required: [], required: [],
additionalProperties: false,
} }
break break
default: default:
@ -212,8 +214,6 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => {
...(schema.properties || {}), ...(schema.properties || {}),
'': { '': {
type: Type.string, type: Type.string,
description: '',
enum: [],
}, },
} }
setHoveringProperty([...path, 'properties', ''].join('.')) setHoveringProperty([...path, 'properties', ''].join('.'))
@ -223,8 +223,6 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => {
...(schema.items.properties || {}), ...(schema.items.properties || {}),
'': { '': {
type: Type.string, type: Type.string,
description: '',
enum: [],
}, },
} }
setHoveringProperty([...path, 'items', 'properties', ''].join('.')) setHoveringProperty([...path, 'items', 'properties', ''].join('.'))
@ -292,6 +290,7 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => {
schema.type = Type.object schema.type = Type.object
schema.properties = {} schema.properties = {}
schema.required = [] schema.required = []
schema.additionalProperties = false
break break
case ArrayType.string: case ArrayType.string:
schema.type = Type.array schema.type = Type.array
@ -317,6 +316,7 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => {
type: Type.object, type: Type.object,
properties: {}, properties: {},
required: [], required: [],
additionalProperties: false,
} }
break break
default: default:
@ -379,6 +379,7 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => {
schema.type = Type.object schema.type = Type.object
schema.properties = {} schema.properties = {}
schema.required = [] schema.required = []
schema.additionalProperties = false
break break
case ArrayType.string: case ArrayType.string:
schema.type = Type.array schema.type = Type.array
@ -404,6 +405,7 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => {
type: Type.object, type: Type.object,
properties: {}, properties: {},
required: [], required: [],
additionalProperties: false,
} }
break break
default: default:

View File

@ -114,8 +114,8 @@ const SchemaNode: FC<SchemaNodeProps> = ({
name, name,
type, type,
required, required,
description: schema.description || '', description: schema.description,
enum: schema.enum || [], enum: schema.enum,
}} }}
path={path} path={path}
parentPath={parentPath!} parentPath={parentPath!}

View File

@ -2,6 +2,7 @@ import { ArrayType, Type } from './types'
import type { ArrayItems, Field, LLMNodeType } from './types' import type { ArrayItems, Field, LLMNodeType } from './types'
import Ajv, { type ErrorObject } from 'ajv' import Ajv, { type ErrorObject } from 'ajv'
import draft7MetaSchema from 'ajv/dist/refs/json-schema-draft-07.json' import draft7MetaSchema from 'ajv/dist/refs/json-schema-draft-07.json'
import produce from 'immer'
export const checkNodeValid = (payload: LLMNodeType) => { export const checkNodeValid = (payload: LLMNodeType) => {
return true return true
@ -91,11 +92,13 @@ const ajv = new Ajv({
ajv.addMetaSchema(draft7MetaSchema) ajv.addMetaSchema(draft7MetaSchema)
export const validateSchemaAgainstDraft7 = (schemaToValidate: any) => { export const validateSchemaAgainstDraft7 = (schemaToValidate: any) => {
const schema = produce(schemaToValidate, (draft: any) => {
// Make sure the schema has the $schema property for draft-07 // Make sure the schema has the $schema property for draft-07
if (!schemaToValidate.$schema) if (!draft.$schema)
schemaToValidate.$schema = 'http://json-schema.org/draft-07/schema#' draft.$schema = 'http://json-schema.org/draft-07/schema#'
})
const valid = ajv.validateSchema(schemaToValidate) const valid = ajv.validateSchema(schema)
return valid ? [] : ajv.errors || [] return valid ? [] : ajv.errors || []
} }