array string validation & update

This commit is contained in:
jZonG 2025-04-26 15:09:41 +08:00
parent a534e58d00
commit fe27604bd3
4 changed files with 157 additions and 47 deletions

View File

@ -14,6 +14,7 @@ type CodeEditorProps = {
showFormatButton?: boolean
editorWrapperClassName?: string
readOnly?: boolean
hideTopMenu?: boolean
} & React.HTMLAttributes<HTMLDivElement>
const CodeEditor: FC<CodeEditorProps> = ({
@ -22,6 +23,7 @@ const CodeEditor: FC<CodeEditorProps> = ({
showFormatButton = true,
editorWrapperClassName,
readOnly = false,
hideTopMenu = false,
className,
}) => {
const { t } = useTranslation()
@ -75,7 +77,8 @@ const CodeEditor: FC<CodeEditorProps> = ({
}, [onUpdate])
return (
<div className={classNames('flex flex-col h-full bg-components-input-bg-normal overflow-hidden', className)}>
<div className={classNames('flex flex-col h-full bg-components-input-bg-normal overflow-hidden', hideTopMenu && 'pt-2', className)}>
{!hideTopMenu && (
<div className='flex items-center justify-between pl-2 pr-1 pt-1'>
<div className='system-xs-semibold-uppercase py-0.5 text-text-secondary'>
<span className='px-1 py-0.5'>JSON</span>
@ -102,6 +105,7 @@ const CodeEditor: FC<CodeEditorProps> = ({
</Tooltip>
</div>
</div>
)}
<div className={classNames('relative', editorWrapperClassName)}>
<Editor
height='100%'

View File

@ -1,21 +1,27 @@
import React, { type FC } from 'react'
import CodeEditor from './code-editor'
import cn from '@/utils/classnames'
type SchemaEditorProps = {
schema: string
onUpdate: (schema: string) => void
hideTopMenu?: boolean
className?: string
}
const SchemaEditor: FC<SchemaEditorProps> = ({
schema,
onUpdate,
hideTopMenu,
className,
}) => {
return (
<CodeEditor
className='rounded-xl'
className={cn('rounded-xl', className)}
editorWrapperClassName='grow'
value={schema}
onUpdate={onUpdate}
hideTopMenu={hideTopMenu}
/>
)
}

View File

@ -0,0 +1,8 @@
import { z } from 'zod'
const arrayStringSchemaParttern = z.array(z.string())
export const validateArrayString = (schema: any) => {
const result = arrayStringSchemaParttern.safeParse(schema)
return result
}

View File

@ -1,7 +1,13 @@
import { useState } from 'react'
import { useEffect, useRef, useState } from 'react'
// import { useTranslation } from 'react-i18next'
import Textarea from '@/app/components/base/textarea'
// import cn from '@/utils/classnames'
import SchemaEditor from '@/app/components/workflow/nodes/llm/components/json-schema-config-modal/schema-editor'
import ErrorMessage from '@/app/components/workflow/nodes/llm/components/json-schema-config-modal/error-message'
import {
validateArrayString,
} from '@/app/components/workflow/variable-inspect/utils'
import { debounce } from 'lodash-es'
import cn from '@/utils/classnames'
export const currentVar = {
id: 'var-jfkldjjfkldaf-dfhekdfj',
@ -10,45 +16,94 @@ export const currentVar = {
// type: 'environment',
name: 'out_put',
// var_type: 'string',
var_type: 'number',
// var_type: 'number',
// var_type: 'object',
// var_type: 'array[string]',
var_type: 'array[string]',
// var_type: 'array[number]',
// var_type: 'array[object]',
// var_type: 'file',
// var_type: 'array[file]',
// value: 'tuituitui',
value: 123,
value: ['aaa', 'bbb', 'ccc'],
// value: {
// abc: '123',
// def: 456,
// fff: true,
// },
edited: true,
}
const ValueContent = () => {
const current = currentVar
const [value, setValue] = useState<any>(current.value ? JSON.stringify(current.value) : '')
const contentContainerRef = useRef<HTMLDivElement>(null)
const errorMessageRef = useRef<HTMLDivElement>(null)
const [editorHeight, setEditorHeight] = useState(0)
const showTextEditor = current.var_type === 'secret' || current.var_type === 'string' || current.var_type === 'number'
const showJSONEditor = current.var_type === 'object' || current.var_type === 'array[string]' || current.var_type === 'array[number]' || current.var_type === 'array[object]'
const showFileEditor = current.var_type === 'file' || current.var_type === 'array[file]'
const handleValueChange = (value: string) => {
const [value, setValue] = useState<any>(current.value ? JSON.stringify(current.value) : '')
const [jsonSchema, setJsonSchema] = useState(current.value || null)
const [json, setJson] = useState(JSON.stringify(jsonSchema, null, 2))
const [parseError, setParseError] = useState<Error | null>(null)
const [validationError, setValidationError] = useState<string>('')
const handleTextChange = (value: string) => {
if (current.var_type === 'string')
setValue(value)
if (current.var_type === 'number') {
if (/^-?\d+(\.)?(\d+)?$/.test(value)) {
console.log(value)
if (/^-?\d+(\.)?(\d+)?$/.test(value))
setValue(value)
}
return
}
if (current.var_type === 'object') {
// TODO update object
const arrayStringValidate = (value: string) => {
try {
const newJSONSchema = JSON.parse(value)
setParseError(null)
const result = validateArrayString(newJSONSchema)
if (!result.success) {
setValidationError(result.error.message)
return false
}
setValidationError('')
return true
}
catch (error) {
setValidationError('')
if (error instanceof Error) {
setParseError(error)
return false
}
else {
setParseError(new Error('Invalid JSON'))
return false
}
}
}
const handleEditorChange = (value: string) => {
if (current.var_type === 'array[string]') {
// TODO update array[string]
setJson(value)
if (arrayStringValidate(value)) {
const parsed = JSON.parse(value)
setJsonSchema(parsed)
}
return
}
if (current.var_type === 'array[number]') {
// TODO update array[number]
}
if (current.var_type === 'object') {
// TODO update object
}
if (current.var_type === 'array[object]') {
// TODO update array[object]
}
}
const handleFileChange = (value: string) => {
if (current.var_type === 'file') {
// TODO update file
}
@ -57,17 +112,54 @@ const ValueContent = () => {
}
}
// get editor height
useEffect(() => {
if (contentContainerRef.current && errorMessageRef.current) {
const errorMessageObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
const { inlineSize } = entry.borderBoxSize[0]
const height = (contentContainerRef.current as any).clientHeight - inlineSize
setEditorHeight(height)
}
})
errorMessageObserver.observe(errorMessageRef.current)
return () => {
errorMessageObserver.disconnect()
}
}
}, [errorMessageRef.current, setEditorHeight])
return (
<div className='flex h-full flex-col gap-3'>
{(current.var_type === 'secret' || current.var_type === 'string' || current.var_type === 'number') && (
<div
ref={contentContainerRef}
className='flex h-full flex-col'
>
<div className={cn('grow')} style={{ height: `${editorHeight}px` }}>
{showTextEditor && (
<Textarea
readOnly={current.type === 'environment'}
disabled={current.type === 'environment'}
className='h-full grow'
className='h-full'
value={value as any}
onChange={e => handleValueChange(e.target.value)}
onChange={debounce(e => handleTextChange(e.target.value))}
/>
)}
{showJSONEditor && (
<SchemaEditor
className='overflow-y-auto'
hideTopMenu
schema={json}
onUpdate={debounce(handleEditorChange)}
/>
)}
{showFileEditor && (
<div>TODO</div>
)}
</div>
<div ref={errorMessageRef} className='shrink-0'>
{parseError && <ErrorMessage className='mt-1' message={parseError.message} />}
{validationError && <ErrorMessage className='mt-1' message={validationError} />}
</div>
</div>
)
}