feat: replace ajv with jsonschema for JSON validation and update related components

This commit is contained in:
twwu 2025-03-28 15:06:39 +08:00
parent 0319e35b4d
commit 9ee8fa644e
12 changed files with 320 additions and 120 deletions

View File

@ -4,7 +4,7 @@ import cn from '@/utils/classnames'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { RiCloseLine } from '@remixicon/react' import { RiCloseLine } from '@remixicon/react'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import { checkDepth } from '../../utils' import { checkJsonDepth } from '../../utils'
import { JSON_SCHEMA_MAX_DEPTH } from '@/config' import { JSON_SCHEMA_MAX_DEPTH } from '@/config'
import CodeEditor from './code-editor' import CodeEditor from './code-editor'
import ErrorMessage from './error-message' import ErrorMessage from './error-message'
@ -59,7 +59,7 @@ const JsonImporter: FC<JsonImporterProps> = ({
setParseError(new Error('Root must be an object, not an array or primitive value.')) setParseError(new Error('Root must be an object, not an array or primitive value.'))
return return
} }
const maxDepth = checkDepth(parsedJSON) const maxDepth = checkJsonDepth(parsedJSON)
if (maxDepth > JSON_SCHEMA_MAX_DEPTH) { if (maxDepth > JSON_SCHEMA_MAX_DEPTH) {
setParseError({ setParseError({
type: 'error', type: 'error',
@ -72,10 +72,10 @@ const JsonImporter: FC<JsonImporterProps> = ({
setOpen(false) setOpen(false)
} }
catch (e: any) { catch (e: any) {
if (e instanceof SyntaxError) if (e instanceof Error)
setParseError(e) setParseError(e)
else else
setParseError(new Error('Unknown error')) setParseError(new Error('Invalid JSON'))
} }
}, [onSubmit, json]) }, [onSubmit, json])

View File

@ -9,12 +9,20 @@ import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import VisualEditor from './visual-editor' import VisualEditor from './visual-editor'
import SchemaEditor from './schema-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 { MittProvider, VisualEditorContextProvider } from './visual-editor/context'
import ErrorMessage from './error-message' import ErrorMessage from './error-message'
import { useVisualEditorStore } from './visual-editor/store' import { useVisualEditorStore } from './visual-editor/store'
import Toast from '@/app/components/base/toast' import Toast from '@/app/components/base/toast'
import { useGetLanguage } from '@/context/i18n' import { useGetLanguage } from '@/context/i18n'
import { JSON_SCHEMA_MAX_DEPTH } from '@/config'
type JsonSchemaConfigProps = { type JsonSchemaConfigProps = {
defaultSchema?: SchemaRoot defaultSchema?: SchemaRoot
@ -74,18 +82,26 @@ const JsonSchemaConfig: FC<JsonSchemaConfigProps> = ({
if (currentTab === value) return if (currentTab === value) return
if (currentTab === SchemaView.JsonSchema) { if (currentTab === SchemaView.JsonSchema) {
try { try {
const parsedJson = JSON.parse(json) const schema = JSON.parse(json)
const schema = convertBooleanToString(parsedJson)
setParseError(null) setParseError(null)
const ajvError = validateSchemaAgainstDraft7(schema) const result = preValidateSchema(schema)
if (ajvError.length > 0) { if (!result.success) {
setValidationError(getValidationErrorMessage(ajvError)) setValidationError(result.error.message)
return return
} }
else { const schemaDepth = checkJsonSchemaDepth(schema)
setJsonSchema(schema) if (schemaDepth > JSON_SCHEMA_MAX_DEPTH) {
setValidationError('') 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) { catch (error) {
setValidationError('') setValidationError('')
@ -135,7 +151,7 @@ const JsonSchemaConfig: FC<JsonSchemaConfigProps> = ({
const handleResetDefaults = useCallback(() => { const handleResetDefaults = useCallback(() => {
if (currentTab === SchemaView.VisualEditor) { if (currentTab === SchemaView.VisualEditor) {
setHoveringProperty('') setHoveringProperty(null)
advancedEditing && setAdvancedEditing(false) advancedEditing && setAdvancedEditing(false)
isAddingNewField && setIsAddingNewField(false) isAddingNewField && setIsAddingNewField(false)
} }
@ -153,15 +169,24 @@ const JsonSchemaConfig: FC<JsonSchemaConfigProps> = ({
try { try {
schema = JSON.parse(json) schema = JSON.parse(json)
setParseError(null) setParseError(null)
const ajvError = validateSchemaAgainstDraft7(schema) const result = preValidateSchema(schema)
if (ajvError.length > 0) { if (!result.success) {
setValidationError(getValidationErrorMessage(ajvError)) setValidationError(result.error.message)
return return
} }
else { const schemaDepth = checkJsonSchemaDepth(schema)
setJsonSchema(schema) if (schemaDepth > JSON_SCHEMA_MAX_DEPTH) {
setValidationError('') 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) { catch (error) {
setValidationError('') setValidationError('')

View File

@ -47,14 +47,13 @@ const GeneratedResult: FC<GeneratedResultProps> = ({
const jsonSchema = useMemo(() => formatJSON(schema), [schema]) const jsonSchema = useMemo(() => formatJSON(schema), [schema])
const handleApply = useCallback(() => { const handleApply = useCallback(() => {
const ajvError = validateSchemaAgainstDraft7(schema) const validationErrors = validateSchemaAgainstDraft7(schema)
if (ajvError.length > 0) { if (validationErrors.length > 0) {
setValidationError(getValidationErrorMessage(ajvError)) setValidationError(getValidationErrorMessage(validationErrors))
} return
else {
onApply()
setValidationError('')
} }
onApply()
setValidationError('')
}, [schema, onApply]) }, [schema, onApply])
return ( return (

View File

@ -47,6 +47,15 @@ const TYPE_OPTIONS = [
{ value: ArrayType.object, text: 'array[object]' }, { 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<EditCardProps> = ({ const EditCard: FC<EditCardProps> = ({
fields, fields,
depth, depth,
@ -63,7 +72,8 @@ const EditCard: FC<EditCardProps> = ({
const { emit, useSubscribe } = useMittContext() const { emit, useSubscribe } = useMittContext()
const blurWithActions = useRef(false) 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 hasAdvancedOptions = currentFields.type === Type.string || currentFields.type === Type.number
const isAdvancedEditing = advancedEditing || isAddingNewField const isAdvancedEditing = advancedEditing || isAddingNewField
@ -205,7 +215,7 @@ const EditCard: FC<EditCardProps> = ({
/> />
<TypeSelector <TypeSelector
currentValue={currentFields.type} currentValue={currentFields.type}
items={TYPE_OPTIONS} items={maximumDepthReached ? MAXIMUM_DEPTH_TYPE_OPTIONS : TYPE_OPTIONS}
onSelect={handleTypeChange} onSelect={handleTypeChange}
popupClassName={'z-[1000]'} popupClassName={'z-[1000]'}
/> />

View File

@ -19,7 +19,7 @@ const VisualEditor: FC<VisualEditorProps> = (props) => {
schema={schema} schema={schema}
required={false} required={false}
path={[]} path={[]}
depth={1} depth={0}
/> />
</div> </div>
) )

View File

@ -23,28 +23,31 @@ type SchemaNodeProps = {
// Support 10 levels of indentation // Support 10 levels of indentation
const indentPadding: Record<number, string> = { const indentPadding: Record<number, string> = {
1: 'pl-0', 0: 'pl-0',
2: 'pl-[20px]', 1: 'pl-[20px]',
3: 'pl-[40px]', 2: 'pl-[40px]',
4: 'pl-[60px]', 3: 'pl-[60px]',
5: 'pl-[80px]', 4: 'pl-[80px]',
6: 'pl-[100px]', 5: 'pl-[100px]',
7: 'pl-[120px]', 6: 'pl-[120px]',
8: 'pl-[140px]', 7: 'pl-[140px]',
9: 'pl-[160px]', 8: 'pl-[160px]',
10: 'pl-[180px]', 9: 'pl-[180px]',
10: 'pl-[200px]',
} }
const indentLeft: Record<number, string> = { const indentLeft: Record<number, string> = {
2: 'left-0', 0: 'left-0',
3: 'left-[20px]', 1: 'left-[20px]',
4: 'left-[40px]', 2: 'left-[40px]',
5: 'left-[60px]', 3: 'left-[60px]',
6: 'left-[80px]', 4: 'left-[80px]',
7: 'left-[100px]', 5: 'left-[100px]',
8: 'left-[120px]', 6: 'left-[120px]',
9: 'left-[140px]', 7: 'left-[140px]',
10: 'left-[160px]', 8: 'left-[160px]',
9: 'left-[180px]',
10: 'left-[200px]',
} }
const SchemaNode: FC<SchemaNodeProps> = ({ const SchemaNode: FC<SchemaNodeProps> = ({
@ -61,13 +64,13 @@ const SchemaNode: FC<SchemaNodeProps> = ({
const isAddingNewField = useVisualEditorStore(state => state.isAddingNewField) const isAddingNewField = useVisualEditorStore(state => state.isAddingNewField)
const advancedEditing = useVisualEditorStore(state => state.advancedEditing) const advancedEditing = useVisualEditorStore(state => state.advancedEditing)
const { run: setHoveringPropertyDebounced } = useDebounceFn((path: string) => { const { run: setHoveringPropertyDebounced } = useDebounceFn((path: string | null) => {
setHoveringProperty(path) setHoveringProperty(path)
}, { wait: 50 }) }, { wait: 50 })
const hasChildren = useMemo(() => getHasChildren(schema), [schema]) const hasChildren = useMemo(() => getHasChildren(schema), [schema])
const type = useMemo(() => getFieldType(schema), [schema]) const type = useMemo(() => getFieldType(schema), [schema])
const isHovering = hoveringProperty === path.join('.') && depth > 1 const isHovering = hoveringProperty === path.join('.')
const handleExpand = () => { const handleExpand = () => {
setIsExpanded(!isExpanded) setIsExpanded(!isExpanded)
@ -80,16 +83,16 @@ const SchemaNode: FC<SchemaNodeProps> = ({
const handleMouseLeave = () => { const handleMouseLeave = () => {
if (advancedEditing || isAddingNewField) return if (advancedEditing || isAddingNewField) return
setHoveringPropertyDebounced('') setHoveringPropertyDebounced(null)
} }
return ( return (
<div className='relative'> <div className='relative'>
<div className={classNames('relative z-10', indentPadding[depth])}> <div className={classNames('relative z-10', indentPadding[depth])}>
{depth > 1 && hasChildren && ( {depth > 0 && hasChildren && (
<div className={classNames( <div className={classNames(
'flex items-center absolute top-0 w-5 h-7 px-0.5 z-10 bg-background-section-burn', 'flex items-center absolute top-0 w-5 h-7 px-0.5 z-10 bg-background-section-burn',
indentLeft[depth], indentLeft[depth - 1],
)}> )}>
<button <button
onClick={handleExpand} onClick={handleExpand}
@ -108,7 +111,7 @@ const SchemaNode: FC<SchemaNodeProps> = ({
onMouseEnter={handleMouseEnter} onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave} onMouseLeave={handleMouseLeave}
> >
{isHovering ? ( {(isHovering && depth > 0) ? (
<EditCard <EditCard
fields={{ fields={{
name, name,
@ -135,7 +138,7 @@ const SchemaNode: FC<SchemaNodeProps> = ({
<div className={classNames( <div className={classNames(
'flex justify-center w-5 absolute top-7 z-0', 'flex justify-center w-5 absolute top-7 z-0',
schema.description ? 'h-[calc(100%-3rem)]' : 'h-[calc(100%-1.75rem)]', schema.description ? 'h-[calc(100%-3rem)]' : 'h-[calc(100%-1.75rem)]',
indentLeft[depth + 1], indentLeft[depth],
)}> )}>
<Divider <Divider
type='vertical' type='vertical'
@ -180,7 +183,7 @@ const SchemaNode: FC<SchemaNodeProps> = ({
)} )}
{ {
depth === 1 && !isAddingNewField && ( depth === 0 && !isAddingNewField && (
<AddField /> <AddField />
) )
} }

View File

@ -4,8 +4,8 @@ import type { SchemaRoot } from '../../../types'
import { VisualEditorContext } from './context' import { VisualEditorContext } from './context'
type VisualEditorStore = { type VisualEditorStore = {
hoveringProperty: string | '' hoveringProperty: string | null
setHoveringProperty: (propertyPath: string) => void setHoveringProperty: (propertyPath: string | null) => void
isAddingNewField: boolean isAddingNewField: boolean
setIsAddingNewField: (isAdding: boolean) => void setIsAddingNewField: (isAdding: boolean) => void
advancedEditing: boolean advancedEditing: boolean
@ -15,8 +15,8 @@ type VisualEditorStore = {
} }
export const createVisualEditorStore = () => createStore<VisualEditorStore>(set => ({ export const createVisualEditorStore = () => createStore<VisualEditorStore>(set => ({
hoveringProperty: '', hoveringProperty: null,
setHoveringProperty: (propertyPath: string) => set({ hoveringProperty: propertyPath }), setHoveringProperty: (propertyPath: string | null) => set({ hoveringProperty: propertyPath }),
isAddingNewField: false, isAddingNewField: false,
setIsAddingNewField: (isAdding: boolean) => set({ isAddingNewField: isAdding }), setIsAddingNewField: (isAdding: boolean) => set({ isAddingNewField: isAdding }),
advancedEditing: false, advancedEditing: false,

View File

@ -1,8 +1,9 @@
import { ArrayType, Type } from './types' import { ArrayType, Type } from './types'
import type { ArrayItems, Field, LLMNodeType } from './types' import type { ArrayItems, Field, LLMNodeType } from './types'
import type { ErrorObject } from 'ajv' import type { Schema, ValidationError } from 'jsonschema'
import { validateDraft07 } from '@/public/validate-esm.mjs' import { Validator } from 'jsonschema'
import produce from 'immer' import produce from 'immer'
import { z } from 'zod'
export const checkNodeValid = (payload: LLMNodeType) => { export const checkNodeValid = (payload: LLMNodeType) => {
return true return true
@ -58,22 +59,37 @@ export const jsonToSchema = (json: any): Field => {
return schema return schema
} }
export const checkDepth = (json: any, currentDepth = 1) => { export const checkJsonDepth = (json: any) => {
const type = inferType(json) if (!json || typeof json !== 'object')
if (type !== Type.object && type !== Type.array) return 0
return currentDepth
let maxDepth = currentDepth let maxDepth = 0
if (type === Type.object) {
Object.keys(json).forEach((key) => { if (Array.isArray(json) && json[0] && typeof json[0] === 'object') {
const depth = checkDepth(json[key], currentDepth + 1) maxDepth = checkJsonDepth(json[0]) + 1
maxDepth = Math.max(maxDepth, depth)
})
} }
else if (type === Type.array && json.length > 0) { else if (typeof json === 'object') {
const depth = checkDepth(json[0], currentDepth + 1) const propertyDepths = Object.values(json).map(value => checkJsonDepth(value))
maxDepth = Math.max(maxDepth, depth) maxDepth = propertyDepths.length ? Math.max(...propertyDepths) + 1 : 1
} }
return maxDepth
}
export const checkJsonSchemaDepth = (schema: Field) => {
if (!schema || typeof schema !== 'object')
return 0
let maxDepth = 0
if (schema.type === Type.object && schema.properties) {
const propertyDepths = Object.values(schema.properties).map(value => checkJsonSchemaDepth(value))
maxDepth = propertyDepths.length ? Math.max(...propertyDepths) + 1 : 1
}
else if (schema.type === Type.array && schema.items && schema.items.type === Type.object) {
maxDepth = checkJsonSchemaDepth(schema.items) + 1
}
return maxDepth return maxDepth
} }
@ -84,6 +100,169 @@ export const findPropertyWithPath = (target: any, path: string[]) => {
return current return current
} }
const draft07MetaSchema = {
$schema: 'http://json-schema.org/draft-07/schema#',
$id: 'http://json-schema.org/draft-07/schema#',
title: 'Core schema meta-schema',
definitions: {
schemaArray: {
type: 'array',
minItems: 1,
items: { $ref: '#' },
},
nonNegativeInteger: {
type: 'integer',
minimum: 0,
},
nonNegativeIntegerDefault0: {
allOf: [
{ $ref: '#/definitions/nonNegativeInteger' },
{ default: 0 },
],
},
simpleTypes: {
enum: [
'array',
'boolean',
'integer',
'null',
'number',
'object',
'string',
],
},
stringArray: {
type: 'array',
items: { type: 'string' },
uniqueItems: true,
default: [],
},
},
type: ['object', 'boolean'],
properties: {
$id: {
type: 'string',
format: 'uri-reference',
},
$schema: {
type: 'string',
format: 'uri',
},
$ref: {
type: 'string',
format: 'uri-reference',
},
title: {
type: 'string',
},
description: {
type: 'string',
},
default: true,
readOnly: {
type: 'boolean',
default: false,
},
examples: {
type: 'array',
items: true,
},
multipleOf: {
type: 'number',
exclusiveMinimum: 0,
},
maximum: {
type: 'number',
},
exclusiveMaximum: {
type: 'number',
},
minimum: {
type: 'number',
},
exclusiveMinimum: {
type: 'number',
},
maxLength: { $ref: '#/definitions/nonNegativeInteger' },
minLength: { $ref: '#/definitions/nonNegativeIntegerDefault0' },
pattern: {
type: 'string',
format: 'regex',
},
additionalItems: { $ref: '#' },
items: {
anyOf: [
{ $ref: '#' },
{ $ref: '#/definitions/schemaArray' },
],
default: true,
},
maxItems: { $ref: '#/definitions/nonNegativeInteger' },
minItems: { $ref: '#/definitions/nonNegativeIntegerDefault0' },
uniqueItems: {
type: 'boolean',
default: false,
},
contains: { $ref: '#' },
maxProperties: { $ref: '#/definitions/nonNegativeInteger' },
minProperties: { $ref: '#/definitions/nonNegativeIntegerDefault0' },
required: { $ref: '#/definitions/stringArray' },
additionalProperties: { $ref: '#' },
definitions: {
type: 'object',
additionalProperties: { $ref: '#' },
default: {},
},
properties: {
type: 'object',
additionalProperties: { $ref: '#' },
default: {},
},
patternProperties: {
type: 'object',
additionalProperties: { $ref: '#' },
propertyNames: { format: 'regex' },
default: {},
},
dependencies: {
type: 'object',
additionalProperties: {
anyOf: [
{ $ref: '#' },
{ $ref: '#/definitions/stringArray' },
],
},
},
propertyNames: { $ref: '#' },
const: true,
enum: {
type: 'array',
items: true,
minItems: 1,
uniqueItems: true,
},
type: {
anyOf: [
{ $ref: '#/definitions/simpleTypes' },
{
type: 'array',
items: { $ref: '#/definitions/simpleTypes' },
minItems: 1,
uniqueItems: true,
},
],
},
format: { type: 'string' },
allOf: { $ref: '#/definitions/schemaArray' },
anyOf: { $ref: '#/definitions/schemaArray' },
oneOf: { $ref: '#/definitions/schemaArray' },
not: { $ref: '#' },
},
default: true,
} as unknown as Schema
const validator = new Validator()
export const validateSchemaAgainstDraft7 = (schemaToValidate: any) => { export const validateSchemaAgainstDraft7 = (schemaToValidate: any) => {
const schema = produce(schemaToValidate, (draft: 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
@ -91,17 +270,20 @@ export const validateSchemaAgainstDraft7 = (schemaToValidate: any) => {
draft.$schema = 'http://json-schema.org/draft-07/schema#' draft.$schema = 'http://json-schema.org/draft-07/schema#'
}) })
const valid = validateDraft07(schema) const result = validator.validate(schema, draft07MetaSchema, {
nestedErrors: true,
throwError: false,
})
// Access errors from the validation result // Access errors from the validation result
const errors = valid ? [] : (validateDraft07 as any).errors || [] const errors = result.valid ? [] : result.errors || []
return errors return errors
} }
export const getValidationErrorMessage = (errors: ErrorObject[]) => { export const getValidationErrorMessage = (errors: ValidationError[]) => {
const message = errors.map((error) => { const message = errors.map((error) => {
return `Error: ${error.instancePath} ${error.message} Details: ${JSON.stringify(error.params)}` return `Error: ${error.path.join('.')} ${error.message} Details: ${JSON.stringify(error.stack)}`
}).join('; ') }).join('; ')
return message return message
} }
@ -125,3 +307,15 @@ export const convertBooleanToString = (schema: any) => {
} }
return schema return schema
} }
const schemaRootObject = z.object({
type: z.literal('object'),
properties: z.record(z.string(), z.any()),
required: z.array(z.string()),
additionalProperties: z.boolean().optional(),
})
export const preValidateSchema = (schema: any) => {
const result = schemaRootObject.safeParse(schema)
return result
}

View File

@ -1,35 +0,0 @@
const fs = require('node:fs')
const path = require('node:path')
const Ajv = require('ajv')
const standaloneCode = require('ajv/dist/standalone').default
const ajv = new Ajv({
allErrors: true,
verbose: true,
code: { source: true, esm: true },
})
const moduleCode = standaloneCode(ajv, {
validateDraft07: 'http://json-schema.org/draft-07/schema#',
})
/**
* @see {@link https://github.com/ajv-validator/ajv/issues/2209}
*/
const preamble = [
'"use strict";',
].join('')
const imports = new Set()
const requireRegex = /const (\S+)\s*=\s*require\((.+)\)\.(\S+);/g
const replaced = moduleCode
.replace(requireRegex, (_match, p1, p2, p3) => {
imports.add(`import { ${p3} as ${p1} } from ${p2};`)
return ''
})
.replace('"use strict";', '')
const uglyOutput = [preamble, Array.from(imports).join(''), replaced].join(
'',
)
fs.writeFileSync(path.join(__dirname, '../public/validate-esm.mjs'), uglyOutput)

View File

@ -56,7 +56,6 @@
"@tanstack/react-query": "^5.60.5", "@tanstack/react-query": "^5.60.5",
"@tanstack/react-query-devtools": "^5.60.5", "@tanstack/react-query-devtools": "^5.60.5",
"ahooks": "^3.8.4", "ahooks": "^3.8.4",
"ajv": "^8.17.1",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"classnames": "^2.5.1", "classnames": "^2.5.1",
"copy-to-clipboard": "^3.3.3", "copy-to-clipboard": "^3.3.3",
@ -74,6 +73,7 @@
"immer": "^9.0.19", "immer": "^9.0.19",
"js-audio-recorder": "^1.0.7", "js-audio-recorder": "^1.0.7",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"jsonschema": "^1.5.0",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"katex": "^0.16.21", "katex": "^0.16.21",
"ky": "^1.7.2", "ky": "^1.7.2",

11
web/pnpm-lock.yaml generated
View File

@ -103,9 +103,6 @@ importers:
ahooks: ahooks:
specifier: ^3.8.4 specifier: ^3.8.4
version: 3.8.4(react@19.0.0) version: 3.8.4(react@19.0.0)
ajv:
specifier: ^8.17.1
version: 8.17.1
class-variance-authority: class-variance-authority:
specifier: ^0.7.0 specifier: ^0.7.0
version: 0.7.0 version: 0.7.0
@ -157,6 +154,9 @@ importers:
js-cookie: js-cookie:
specifier: ^3.0.5 specifier: ^3.0.5
version: 3.0.5 version: 3.0.5
jsonschema:
specifier: ^1.5.0
version: 1.5.0
jwt-decode: jwt-decode:
specifier: ^4.0.0 specifier: ^4.0.0
version: 4.0.0 version: 4.0.0
@ -5854,6 +5854,9 @@ packages:
jsonfile@6.1.0: jsonfile@6.1.0:
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
jsonschema@1.5.0:
resolution: {integrity: sha512-K+A9hhqbn0f3pJX17Q/7H6yQfD/5OXgdrR5UE12gMXCiN9D5Xq2o5mddV2QEcX/bjla99ASsAAQUyMCCRWAEhw==}
jsx-ast-utils@3.3.5: jsx-ast-utils@3.3.5:
resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
engines: {node: '>=4.0'} engines: {node: '>=4.0'}
@ -15253,6 +15256,8 @@ snapshots:
optionalDependencies: optionalDependencies:
graceful-fs: 4.2.11 graceful-fs: 4.2.11
jsonschema@1.5.0: {}
jsx-ast-utils@3.3.5: jsx-ast-utils@3.3.5:
dependencies: dependencies:
array-includes: 3.1.8 array-includes: 3.1.8

File diff suppressed because one or more lines are too long